您的位置:首页 > 编程语言 > Delphi

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

2016-02-08 16:31 483 查看
在研究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;
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐