终于懂了: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 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;

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

如果是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
        begin
          IntersectRect(R, Rect, BoundsRect);
          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;
时间: 2024-12-27 10:55:22

终于懂了:FWinControls子控件的显示是由Windows来管理,而不是由Delphi来管理的相关文章

解决TalbleView头部或底部子控件不显示问题

在自定义cell头部控件UITableViewHeaderFooterView(和自定义cell的方法几乎一样)时,出现了头部控件子控件不显示的问题. 注意和自定义cell的区别. .h文件 1 #import <UIKit/UIKit.h> 2 #import "CHModleGroup.h" 3 @interface HeaderView : UITableViewHeaderFooterView 4 @property (nonatomic, weak) UILabe

Android自定义组合控件内子控件无法显示问题

今天自定义了一个组合控件,与到了个奇葩问题: 我自定义了一个RelativeLayout,这个layout内有多个子控件.但奇怪的是这些子控件一直显示不出来.调试了一下午,竟然是因为在获取(inflate)布局时没有添加到Root.

记录下UIButton的图文妙用和子控件的优先显示

UIButton的用处特别多,这里只记录下把按钮应用在图文显示的场景,和需要把图片作为按钮的背景图片显示场景: 另外记录下在父控件的子控件优先显示方法(控件置于最前面和置于最后面). 先上效果图: 1.当在某个地方既需要显示图片,还需要显示文字,另外还要有点击功能的时候,这时按钮是个很好的选择. 按钮中的图片和文字的距离可以自由调整,图片的也可以上下左右翻转.日常项目中像这些场景都是很容易碰到的. 按钮图文设置.图文位置移动.按钮中图片翻转示例代码: /** 测试图文并茂的按钮,图文移动 */

五种情况下会刷新控件状态(刷新所有子FWinControls的显示)——从DFM读取数据时、新增加子控件时、重新创建当前控件的句柄时、设置父控件时、显示状态被改变时

五种情况下会刷新控件状态(刷新控件状态才能刷新所有子FWinControls的显示): 在TWinControls.PaintControls中,对所有FWinControls只是重绘了边框,而没有整个重绘这些FWinControl子控件.那么什么时候才整个重绘全部FWinControls呢?这时候,就不是一个单纯的WM_PAINT来解决控件重绘的问题了,而是这个TWinControl.UpdateShowing函数: procedure TWinControl.UpdateShowing; v

IOS 读取xib里的子控件

interface ViewController () /**获取.plist数据*/ @property (nonatomic,strong) NSArray *aps; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; //九宫格的总列数 int totalColumns=5; //1.1个格子的尺寸 CGFloat appW=50; CGFloat appH=60; //2.计算间隙

父控件、子控件

1 每一个控件其实都是一个容器可以将其他控件放到该控件的内部比较常见的还是将UIView作为容器 2 可以将A控件放入B控件A控件是B控件的子控件B控件是A控件的父控件 3 每一个控制器都有一个UIView控制器本身是不可见能够看到的是控制器的View每一个控制器中都一个UIVIew的属性控制器中管理的所有子控件都是该控件的子控件

Android--根据子控件的大小自动换行的ViewGroup

1.自定义ViewGroup 1 /** 2 * Created by Administrator on 2016/2/26. 3 * 4 * --------自动换行的ViewGroup----------- 5 */ 6 public class LineWrapLayout extends ViewGroup { 7 private static final boolean DEBUG = true; 8 private static final String TAG = "AutoLin

[转]Android ListView最佳处理方式,ListView拖动防重复数据显示,单击响应子控件

Android ListView最佳处理方式,ListView拖动防重复数据显示,单击响应子控件. 1.为了防止拖动ListView时,在列表末尾重复数据显示.需要加入 HashMap<Integer,View> lmap = new HashMap<Integer,View>();其中Integer为列表位置,View为子项视图,加入数据前首先if (lmap.get(position)==null) ,满足条件时,加入lmap.put(position, convertView

xib或storyBoard中往scrollView添加子控件

如何在xib或者storyBoard中的scrollView加拖拽子控件,并且能让它可以正常滚动,那么下面就为大家简单演示: 运行环境为:Xcode 7.3 备注:图片较大,如果看不清看可以右键鼠标在新标签中打开图片就很清晰了 1.如图我们往storyBoard拖拽一个scrollView并设置为上下左右约束距离父控件为0 2.当我们往scrollView中添加子控件时候却发现报错了 因为scrollView比较特殊,需要设置contentSize,这个错误和平常我们遇到的错误不一样,因为在这两