TForm的显示过程
新建一个空窗体项目,然后运行,此时首先运行:
procedure TApplication.Run; begin FRunning := True; try AddExitProc(DoneApplication); if FMainForm <> nil then begin case CmdShow of SW_SHOWMINNOACTIVE: FMainForm.FWindowState := wsMinimized; SW_SHOWMAXIMIZED: MainForm.WindowState := wsMaximized; end; if FShowMainForm then if FMainForm.FWindowState = wsMinimized then Minimize else FMainForm.Visible := True; repeat try HandleMessage; except HandleException(Self); end; until Terminated; end; finally FRunning := False; end; end;
调用 MainForm.WindowState := wsMaximized;
其中 类属性WindowState调用SetWindowState
调用 FMainForm.Visible := True;
其中 类属性Visible调用SetVisible虚函数,间接调用TControl.SetVisible(相当于UpdateWindow API)
第一个步骤:
procedure TCustomForm.SetWindowState(Value: TWindowState); const ShowCommands: array[TWindowState] of Integer = (SW_SHOWNORMAL, SW_MINIMIZE, SW_SHOWMAXIMIZED); begin if FWindowState <> Value then begin FWindowState := Value; if not (csDesigning in ComponentState) and Showing then ShowWindow(Handle, ShowCommands[Value]); end; end;
第二个步骤::
procedure TCustomForm.SetVisible(Value: Boolean); begin if fsCreating in FFormState then if Value then Include(FFormState, fsVisible) else Exclude(FFormState, fsVisible) else begin if Value and (Visible <> Value) then SetWindowToMonitor; inherited Visible := Value; end; end; procedure TControl.SetVisible(Value: Boolean); begin if FVisible <> Value then begin VisibleChanging; FVisible := Value; Perform(CM_VISIBLECHANGED, Ord(Value), 0); RequestAlign; 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; 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; end; 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;
---------------------------------------------------------------------------------------------------
第三步,调用了ShowWindow API和SetWindowPos API以后(不知道这两个API那个更重要),当系统空闲时(因为没发现调用UpdateWindow API),Windows先擦除Form1的背景,后向Form1发WM_PAINT消息,由TCustomForm接收:
procedure TWinControl.WMEraseBkgnd(var Message: TWMEraseBkgnd); begin with ThemeServices do if ThemesEnabled and Assigned(Parent) and (csParentBackground in FControlStyle) then begin { Get the parent to draw its background into the control's background. } DrawParentBackground(Handle, Message.DC, nil, False); end else begin { Only erase background if we're not doublebuffering or painting to memory. } if not FDoubleBuffered or (TMessage(Message).wParam = TMessage(Message).lParam) then FillRect(Message.DC, ClientRect, FBrush.Handle); end; Message.Result := 1; end;
擦除背景绝对重要,只有擦除了背景,才能在上面作画,否则作画全部无效:
procedure TCustomForm.WMPaint(var Message: TWMPaint); var DC: HDC; PS: TPaintStruct; begin if not IsIconic(Handle) then begin ControlState := ControlState + [csCustomPaint]; inherited; ControlState := ControlState - [csCustomPaint]; end else begin DC := BeginPaint(Handle, PS); DrawIcon(DC, 0, 0, GetIconHandle); EndPaint(Handle, PS); end; end;
在TWinControl.WMPaint函数里下调试点:
procedure TWinControl.WMPaint(var Message: TWMPaint); var DC, MemDC: HDC; MemBitmap, OldBitmap: HBITMAP; PS: TPaintStruct; begin if not FDoubleBuffered or (Message.DC <> 0) then begin if not (csCustomPaint in ControlState) and (ControlCount = 0) then inherited else PaintHandler(Message); end else begin DC := GetDC(0); MemBitmap := CreateCompatibleBitmap(DC, ClientRect.Right, ClientRect.Bottom); ReleaseDC(0, DC); MemDC := CreateCompatibleDC(0); OldBitmap := SelectObject(MemDC, MemBitmap); try DC := BeginPaint(Handle, PS); Perform(WM_ERASEBKGND, MemDC, MemDC); Message.DC := MemDC; WMPaint(Message); Message.DC := 0; BitBlt(DC, 0, 0, ClientRect.Right, ClientRect.Bottom, MemDC, 0, 0, SRCCOPY); EndPaint(Handle, PS); finally SelectObject(MemDC, OldBitmap); DeleteDC(MemDC); DeleteObject(MemBitmap); end; end; end;
很明显执行的是 not FDoubleBuffered逻辑,说明TForm的双缓冲默认是关闭的。然后执行PaintHandler
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;
因为是空窗体,所以执行PaintWindow(如有子控件执行PaintControls),即:
procedure TCustomForm.PaintWindow(DC: HDC); begin FCanvas.Lock; try FCanvas.Handle := DC; try if FDesigner <> nil then FDesigner.PaintGrid else Paint; finally FCanvas.Handle := 0; end; finally FCanvas.Unlock; end; end;
最后执行Paint
procedure TCustomForm.Paint; begin if Assigned(FOnPaint) then FOnPaint(Self); end;
然后就通过FOnPaint自动调用了程序员定义的事件。
总结:TForm和TCustomControl有点差不多,都各自包含了一个Canvas,属于自绘控件,至于它们的显示函数的区别如下:
WMPaint函数一样加上了csCustomPaint风格,只是TForm在窗口最小化的情况下是绘制图标 (入口函数,毕竟Windows直接把消息发给这个函数)
PaintWindow函数完全一样(除了TForm在设计期间要显示格子)。
Paint函数略有点不一样,在TCustomControl里完全空函数,反正它就是抽象类,等着被继承和覆盖;在TForm里有内容,即直接调用程序员函数
我的另一篇文章,注释比较详细,可一并参考:一行代码设置TForm颜色的前世今生
http://www.cnblogs.com/findumars/p/4117783.html
--------------------------------------------------------------------
其中有意思的一个小技巧是调用父类属性:
TCustomForm.SetVisible(Value: Boolean);函数里,调用父类的属性居然可以这样调用:
inherited Visible := Value;
如果不写inherited,就变成调用本类的属性了,就会调用不同的Set函数,这样效果就完全不一样了。