五种情况下会刷新控件状态(刷新控件状态才能刷新所有子FWinControls的显示):
在TWinControls.PaintControls中,对所有FWinControls只是重绘了边框,而没有整个重绘这些FWinControl子控件。那么什么时候才整个重绘全部FWinControls呢?这时候,就不是一个单纯的WM_PAINT来解决控件重绘的问题了,而是这个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;
那么什么时候才会调用TWinControl.UpdateShowing;呢?回答是,就一种情况下:当控件状态改变的时候:
procedure TWinControl.UpdateControlState; var Control: TWinControl; begin Control := Self; while Control.Parent <> nil do begin Control := Control.Parent; if not Control.Showing then Exit; end; if (Control is TCustomForm) or (Control.FParentWindow <> 0) then UpdateShowing; // 必须有父窗口,才给显示。TCustomForm那已经是顶级窗口 end;
那么什么时候算控件状态改变?回答是,一共五种情况:从DFM读取数据时、新增加子控件时、重新创建当前控件的句柄时、设置父控件时、显示状态被改变时:
// 情况一: procedure TWinControl.ReadState(Reader: TReader); begin DisableAlign; try inherited ReadState(Reader); finally EnableAlign; end; FixupTabList; if FParent <> nil then Perform(CM_PARENTCTL3DCHANGED, 0, 0); UpdateControlState; end; // 情况二: procedure TWinControl.InsertControl(AControl: TControl); begin AControl.ValidateContainer(Self); Perform(CM_CONTROLLISTCHANGE, Integer(AControl), Integer(True)); Insert(AControl); if not (csReading in AControl.ComponentState) then begin AControl.Perform(CM_PARENTCOLORCHANGED, 0, 0); AControl.Perform(CM_PARENTFONTCHANGED, 0, 0); AControl.Perform(CM_PARENTSHOWHINTCHANGED, 0, 0); AControl.Perform(CM_PARENTBIDIMODECHANGED, 0, 0); if AControl is TWinControl then begin AControl.Perform(CM_PARENTCTL3DCHANGED, 0, 0); UpdateControlState; end else if HandleAllocated then AControl.Invalidate; AlignControl(AControl); end; Perform(CM_CONTROLCHANGE, Integer(AControl), Integer(True)); end; // 情况三: procedure TWinControl.CMRecreateWnd(var Message: TMessage); var WasFocused: Boolean; begin WasFocused := Focused; DestroyHandle; UpdateControlState; if WasFocused and (FHandle <> 0) then Windows.SetFocus(FHandle); end; // 情况四: procedure TWinControl.SetParentWindow(Value: HWnd); begin if (FParent = nil) and (FParentWindow <> Value) then begin if (FHandle <> 0) and (FParentWindow <> 0) and (Value <> 0) then begin FParentWindow := Value; Windows.SetParent(FHandle, Value); if (Win32MajorVersion >= 5) and (Win32Platform = VER_PLATFORM_WIN32_NT) then Perform(WM_CHANGEUISTATE, MakeWParam(UIS_INITIALIZE, UISF_HIDEACCEL or UISF_HIDEFOCUS), 0); end else begin DestroyHandle; FParentWindow := Value; end; UpdateControlState; end; end; // 情况五: procedure TWinControl.CMVisibleChanged(var Message: TMessage); begin if not FVisible and (Parent <> nil) then RemoveFocus(False); if not (csDesigning in ComponentState) or (csNoDesignVisible in ControlStyle) then UpdateControlState; end;
但是还是有个问题,在TWinControl.UpdateShowing里执行了CM_SHOWINGCHANGED消息,也就是:
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;
可是SetWindowPos会触发WM_PAINT消息吗?看MSDN没有这么说啊,那么显示所有WinControl子控件和调用它的WM_PAINT真正自绘,两者怎样才能联系起来呢?