终于懂了:FWinControls子控件的显示是由Windows来管理,而不是由Delphi来管理(显示透明会导致计算无效区域的方式有所不同——透明的话应减少剪裁区域,所以要进行仔细计算)
2016-02-08 16:31
483 查看
在研究TCustomControl的显示过程中,怎么样都找不到刷新FWinControls并重新显示的代码:
就连在TWinControl.UpdateShowing里也找不到相关代码:
后来在火车上想啊想,忽然灵机一动,明白了这些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在属性变化的时候,就会手动执行:
这样就强迫父窗口刷新这个图形控件的显示。
如果是Invalidate和Update,其本质不变:
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;
相关文章推荐
- Delphi 序列化、反序列化、串行化、持久化
- DELPHI语法基础学习笔记-Windows 句柄、回调函数、函数重载等(Delphi中很少需要直接使用句柄,因为句柄藏在窗体、 位图及其他Delphi 对象的内部)
- Delphi体系内部的4种消息传递办法(Send,Post,Perform,Dispatch)
- delphi高手突破之异常及错误处理
- delphi如何加上spliter分割条,任意调整大小
- delphi高手突破学习笔记之面向对象类和对象的本质(有汇编解释 good)
- delphi模态窗体最小化会隐藏的问题
- 终于懂了:Delphi消息的Result完全是生造出来的,不是Windows消息自带的(Delphi对Windows编程体系的改造越大,学习收获就越大)
- 终于懂了:Delphi重定义消息结构随心所欲,只需要前4个字节是消息编号就行了,跟Windows消息虽然尽量保持一致,但其实相互没有特别大的关系。有了这个,就有了主动,带不带句柄完全看需要。
- WM_PAINT在微软官方定义中,wParam和lParam都没有使用,所以就被Delphi给重定义了这个消息,还增加了DC(Delphi可任意改写消息的结构,只需要保持前4个字节是消息即可,另外要携带微软定义的所有必要信息就行了)
- WM_PAINT中应该用BeginPaint与EndPaint这两个api,它们的功能正是使无效区域恢复(所以WM_PAINT里即使什么都不做,也必须写上BeginPaint与EndPaint)——Delphi里WM_PAINT消息的三个走向都做到了这一点
- Delphi和C++数据类型对照表
- Delphi中如何获得光标
- 利用Delphi编写Socket通信程序
- (delphi)DbgridEh排序
- ListView 百分比进度条(delphi版)
- Delphi的RTTI还分为对类和对象的判断,以及对普通属性的判断——相比之下,C++的RTTI实在太弱!
- 终于懂了:TWinControl主要是Delphi官方用来封装Windows的官方控件,开发者还是应该是有TCustomControl来开发三方控件
- C++能在三个地方创造对象,而Delphi只有一个地方
- delphi 怎么获取工程版本号