• 终于懂了:FWinControls子控件的显示是由Windows来管理,而不是由Delphi来管理(显示透明会导致计算无效区域的方式有所不同——透明的话应减少剪裁区域,所以要进行仔细计算)


    在研究TCustomControl的显示过程中,怎么样都找不到刷新FWinControls并重新显示的代码:

    procedure TWinControl.PaintHandler(var Message: TWMPaint);
    var
      I, Clip, SaveIndex: Integer;
      DC: HDC;
      PS: TPaintStruct;
    begin
      DC := Message.DC;
      if DC = 0 then DC := BeginPaint(Handle, PS);
      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);
      finally
        if Message.DC = 0 then EndPaint(Handle, PS);
      end;
    end;
    
    procedure TWinControl.PaintControls(DC: HDC; First: TControl);
    var
      I, Count, SaveIndex: Integer;
      FrameBrush: HBRUSH;
    begin
      if DockSite and UseDockManager and (DockManager <> nil) then
        DockManager.PaintSite(DC);
      if FControls <> nil then
      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
            begin
              if csPaintCopy in Self.ControlState then
                Include(FControlState, csPaintCopy);
              SaveIndex := SaveDC(DC);
              MoveWindowOrg(DC, Left, Top);
              IntersectClipRect(DC, 0, 0, Width, Height);
              Perform(WM_PAINT, DC, 0);
              RestoreDC(DC, SaveIndex);
              Exclude(FControlState, csPaintCopy);
            end;
          Inc(I);
        end;
      end;
      if FWinControls <> nil then
        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 // 只是绘制边框而已
              FrameBrush := CreateSolidBrush(ColorToRGB(clBtnShadow));
              FrameRect(DC, Rect(Left - 1, Top - 1, Left + Width, Top + Height),
                FrameBrush);
              DeleteObject(FrameBrush);
              FrameBrush := CreateSolidBrush(ColorToRGB(clBtnHighlight));
              FrameRect(DC, Rect(Left, Top, Left + Width + 1, Top + Height + 1),
                FrameBrush);
              DeleteObject(FrameBrush);
            end;
    end;

    就连在TWinControl.UpdateShowing里也找不到相关代码:

    procedure TWinControl.UpdateShowing;
    var
      ShowControl: Boolean;
      I: Integer;
    begin
      ShowControl := (FVisible or (csDesigning in ComponentState) and
        not (csNoDesignVisible in ControlStyle)) and
        not (csReadingState in ControlState);
      if ShowControl then
      begin
        if FHandle = 0 then CreateHandle;
        if FWinControls <> nil then
          for I := 0 to FWinControls.Count - 1 do
            TWinControl(FWinControls[I]).UpdateShowing;
      end;
      if FHandle <> 0 then
        if FShowing <> ShowControl then
        begin
          FShowing := ShowControl;
          try
            Perform(CM_SHOWINGCHANGED, 0, 0);
          except
            FShowing := not ShowControl;
            raise;
          end;
        end;
    end;
    
    procedure TWinControl.CMShowingChanged(var Message: TMessage);
    const
      ShowFlags: array[Boolean] of Word = (
        SWP_NOSIZE + SWP_NOMOVE + SWP_NOZORDER + SWP_NOACTIVATE + SWP_HIDEWINDOW,
        SWP_NOSIZE + SWP_NOMOVE + SWP_NOZORDER + SWP_NOACTIVATE + SWP_SHOWWINDOW);
    begin
      SetWindowPos(FHandle, 0, 0, 0, 0, 0, ShowFlags[FShowing]);
    end;

    后来在火车上想啊想,忽然灵机一动,明白了这些FWinControls是由Windows来管理,而不是Delphi管理。

    一个具有Handle的窗口,不仅仅是Delphi的一部分,并且也是在整个Windows中挂了号的。除去首次显示之外(即上面的SetWindowPos,这个得另外研究),这个Windows窗口什么时候需要刷新显示,是由Windows说了算。而Windows只有发现这个Windows窗口具有无效区域的时候,才会对它进行刷新显示。即Windows系统直接对这个Windows窗口发送WM_PAINT消息,而不需要Delphi在VCL体系内部写代码发送WM_PAINT消息。这就是我始终找不到for I := 0 to FWinControls.Count - 1 do Perform(WM_PAINT, 0, 0);或者UpdateWindow()的原因。

    话说是Windows自动判断无效区域才会决定是否刷新这个Windows控件,而造成无效区域的原因有2类:1.程序员调用Invalidate 这类API 2.用户实际操作,造成窗口移动/遮挡/显示等不同的情况。

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

    补充:当一个TWinControl内部包含的图形控件的属性有变化而需要重新显示的时候,Windows就没法知道这些事情了,所以聪明的Delphi在属性变化的时候,就会手动执行:

    procedure TControl.Repaint;
    var
      DC: HDC;
    begin
      if (Visible) and (Parent <> nil) and
        Parent.HandleAllocated then
        if csOpaque in ControlStyle then // 不透明(一般情况下)
        begin
          DC := GetDC(Parent.Handle);
          try // 不透明的话,比较简单,使用一个API直接就可以获得新的无效剪裁区域
            IntersectClipRect(DC, Left, Top, Left + Width, Top + Height); // API 从当前剪裁区域和指定矩形的交叉区域中,创建一个新的剪裁区域
            Parent.PaintControls(DC, Self); // 不管是否具有无效区域,直接发送WM_PAINT要求重绘。我觉得换成调用Self.Update也可以,但是效率会比较低
          finally
            ReleaseDC(Parent.Handle, DC);
          end;
        end else // 透明会导致计算无效区域的方式不同
        begin 
          Invalidate; // 透明的话,应减少剪裁区域,所以要进行仔细计算
          Update;
        end;
    end;

    这样就强迫父窗口刷新这个图形控件的显示。

    如果是Invalidate和Update,其本质不变:

    procedure TControl.Invalidate;
    begin
      InvalidateControl(Visible, csOpaque in ControlStyle);
    end;
    
    procedure TControl.InvalidateControl(IsVisible, IsOpaque: Boolean);
    var
      Rect: TRect;
    
      function BackgroundClipped: Boolean;
      var
        R: TRect;
        List: TList;
        I: Integer;
        C: TControl;
      begin
        Result := True;
        List := FParent.FControls;
        I := List.IndexOf(Self);
        while I > 0 do
        begin
          Dec(I);
          C := List[I];
          with C do
            if C.Visible and (csOpaque in ControlStyle) then // 不透明需要计算,透明就不用计算了(我懂了,透明就是不用管这个控件所占用的整体区域,而是直接使用API绘制,这样不需要Delphi帮忙管其它东西了)
            begin
              IntersectRect(R, Rect, BoundsRect); // API 计算交叉区域,R是其返回值
              if EqualRect(R, Rect) then Exit;
            end;
        end;
        Result := False;
      end;
    
    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)); // API
      end;
    end;
    
    procedure TControl.Update;
    begin
      if Parent <> nil then Parent.Update;
    end;
    
    procedure TWinControl.Update;
    begin
      if HandleAllocated then UpdateWindow(FHandle); // API
    end;
  • 相关阅读:
    内存相关函数
    Redis入门
    libevent(九)evhttp
    Python基础00 教程
    Python之re模块
    Makefile入门
    cmake安装jsoncpp
    awk调用date命令
    SQLite使用(二)
    SQLite使用(一)
  • 原文地址:https://www.cnblogs.com/findumars/p/5185050.html
Copyright © 2020-2023  润新知