TControl是圖形控件,它本身沒有句柄,所以不能直接使用WINAPI顯示,調整位置,發消息等等,只能想辦法間接取得想要的效果,但是可以直接使用一些不需要句柄的API,比如InvalidateRect。
TWinControl是含有Windows句柄的窗口,它有句柄,因此所有使用句柄的WINAPI都可以直接操作它從而取得各種效果,使得Windows窗口能夠被驅動從而正常的工作。所以它順帶把它的圖形子控件管理起來,讓它們在自己所在的一份三分地里正常的工作(我的理解:在Delphi的世界里,此時一個WinControl就相當於扮演了整個Windows的角色,用來管理它的“子窗口”控件,即TControl和TWinControl,如果說它對WinControl的管理功能還不是那么強烈和重要,但它無疑百分百的管理起了TGraphicControl,這一點非常重要)。
--------------------------------------------------------------------------
因為這個原因,TWinControl需要定義一系列的類函數,用它們包裝WINAPI,使之符合Delphi的VCL整體架構,並且還更好用。TControl雖然無法直接使用WINAPI驅動工作,但VCL的作者們偷梁換柱使用它的父控件句柄,並使用相同的函數名稱達到了相同的效果(這就是為什么李維會提到一個VCL的缺陷——TControl.Parent必須是個TWinControl,這是因為他沒有意識到是Borland在有意強迫這樣做,否則難道還要處處判斷這個Parent是TWinControl,很可能還要多寫很多異常語句,而且需要TWinControl自己的函數的時候,還要使用RTTI判斷或者強行轉換一下,那樣豈不是麻煩死。李維大師的思路也沒有錯,那是一種純面向對象理論的思路,但他本人畢竟沒有深入參與VCL開發,跟Borland的大師們還有些距離,而以他的一己之力來解釋整個VCL的架構與思路,而實際上估計Borland早就考慮到了這一點,對Windows編程和OO運用的如此如火純青的人,怎么會連這一點都想不到?所以Delphi的實現方法往往已經就是理論的完美詮釋,因為它對純理論方案的一些缺陷做了最佳的彌補和平衡,而且我感覺處處都是這樣,這點也是我覺得Delphi最了不起的地方。光把理論實現一遍誰不會啊,無非就是花點時間和繁瑣一點。正如有朋友說的那樣,C++以外的世界很精彩,不要一輩子沉迷於C++的世界,有空還是要多學幾門語言並加以思考和比較一下,我就感覺自己從Delphi的一些思想和平衡性方案里受益匪淺。當然只沉迷於Delphi的世界里也是不行的,我的下一個目標是Golang和C#,哈哈哈)。這就是為什么TControl會經常有TWinControl的同名函數,因為相同名稱的函數更好用嘛!比如SetBounds函數(我就是看到這個函數時想到這層意思的):
procedure TControl.SetBounds(ALeft, ATop, AWidth, AHeight: Integer); begin if CheckNewSize(AWidth, AHeight) and // TControl的類函數 ((ALeft <> FLeft) or (ATop <> FTop) or (AWidth <> FWidth) or (AHeight <> FHeight)) then begin InvalidateControl(Visible, False); // TControl的類函數,第二個參數表示暫時設置當前控件是透明的 FLeft := ALeft; FTop := ATop; FWidth := AWidth; FHeight := AHeight; UpdateAnchorRules; // TControl的類函數,坐標和長寬設置完了,就要重新鉚接一下 // 屬性設置完了,如果有API可以使之起作用就當場調用(關於顯示部分,不需要句柄就有API使用,這是特殊情況) Invalidate; // TControl的類函數,調用TControl.InvalidateControl,再調用API聲明無效區域 // 此消息在TControl和TWinControl里都有相應的函數,圖形控件使用消息再做一些自己力所能及的變化,Win控件使用消息調用類函數使之調用API真正起作用 // 前者重新計算最大化最小化的限制和塢里的尺寸,后者使用API調整邊框和控件自己的位置,當然也得重新計算最大化最小化的限制和塢里的尺寸(三明治手法) Perform(WM_WINDOWPOSCHANGED, 0, 0); // Windows位置調整完了,還要重新對齊(本質是調用TWinControl.RequestAlign,然后調用API重新排列) // 但實際上是靠父Win控件重新排列自己,因為它自己沒有能力擁有別的控件,當然也就不能實質上讓所有控件對齊。 RequestAlign; // TControl的虛函數,各WinControl的子類可自己改寫,比如TCustomForm就改寫了 if not (csLoading in ComponentState) then Resize; // TControl的虛函數,簡單調用程序員事件。子類一般不需要改寫它。 end; end;
我們可以看到TWinControl也有相應的函數,它就可以使用API直接達到效果,它甚至還可以使用TControl定義的某些通用邏輯和效果(拜OO技術所賜):
procedure TWinControl.SetBounds(ALeft, ATop, AWidth, AHeight: Integer); var WindowPlacement: TWindowPlacement; // Windows結構類型,包含最大化最小化窗口位置等6項內容 begin if (ALeft <> FLeft) or (ATop <> FTop) or (AWidth <> FWidth) or (AHeight <> FHeight) then begin // 如果有句柄,並且不是最小化狀態,就使用API立刻調整位置 if HandleAllocated and not IsIconic(FHandle) then // API SetWindowPos(FHandle, 0, ALeft, ATop, AWidth, AHeight, SWP_NOZORDER + SWP_NOACTIVATE) // API,使用完畢后Windows自己會更改WindowPlacement里的數據 else // 如果還沒有句柄,或者處於最小化狀態下,使用API更改WindowPlacement結構的值,以備下次調用API直接顯示 begin // 重新設置左上角坐標以及長寬 FLeft := ALeft; // TControl的類屬性,通用屬性 FTop := ATop; FWidth := AWidth; FHeight := AHeight; if HandleAllocated then begin // 取得窗口的位置信息 WindowPlacement.Length := SizeOf(WindowPlacement); GetWindowPlacement(FHandle, @WindowPlacement); // API // 更改窗口的位置信息 WindowPlacement.rcNormalPosition := BoundsRect; // TControl的類屬性,通用屬性 SetWindowPlacement(FHandle, @WindowPlacement); // API end; end; // super 前面使用API設置了真實的Windows窗口屬性和Delphi控件屬性后,可放心大膽的調用一些函數,不是TControl已經提供了通用邏輯,就是它自己定義了一些特殊的函數,可隨便使用,直接產生效果,而不再依賴別人來完成某件事情。 UpdateAnchorRules; // TControl類函數,通用函數 RequestAlign; // TControl類函數,通用函數 end; end;
TControl.GetDeviceContext這個例子也很明顯,它使用父Win控件的同名函數得到DC,然后使用這個句柄調用API得到視角和增加一個新的剪裁區域,因為這兩個API是和自繪有關的API,而TControl的基本設計目的之一就是顯示和剪裁圖形圖像,因此TControl可直接使用它們實現想要的功能。
function TControl.GetDeviceContext(var WindowHandle: HWnd): HDC; begin if Parent = nil then raise EInvalidOperation.CreateFmt(SParentRequired, [Name]); Result := Parent.GetDeviceContext(WindowHandle);// TWinControl的類函數,不僅僅設置DC,還從Delphi的FHandle設置參數句柄 SetViewportOrgEx(Result, Left, Top, nil); // API 用來改變視端口和窗口的原點,並都具有改變軸的效果,以致(0,0)不再指左上角 IntersectClipRect(Result, 0, 0, Width, Height); // API 創建一個新的剪切區域,該區域是當前剪切區域和一個特定矩形的交集 end;
再稍微總結一下TControl的對齊過程,其實也是同理:
RequestAlign->AlignControl->DisableAlign->AlignControls(實質上干活對齊)->EnableAlign->Realign->AlignControl(nil)
整個過程的一級入口函數是:
TControl.RequestAlign;
但它只是圖形控件,沒有能力擁有別的控件,當然也就不能實質上讓所有控件對齊,所以要執行:
TWinControl.AlignControl(AControl: TControl);
它做了整個對齊邏輯的封裝,依次執行:
調用TWinControl.DisableAlign; 它執行Inc(FAlignLevel);
調用TWinControl.AlignControls(AControl: TControl; var Rect: TRect); // 所有有關控件大小、對齊、鉚接的實質內容都在這里
調用TWinControl.EnableAlign; 它調用TWinControl.Realign; 它調用TWinControl.AlignControl(nil);
--------------------------------------------------------------------------
另外,TWinControl也不僅僅代表自己,一定程度上也代表了它所有的子控件。當自己發生某些變化時,可以通知自己的子控件,子控件是否願意響應是它自己的事情,但是作為parent的義務已經盡到了,並且相互之間也做了有效的溝通。比如TWinControl.CMColorChanged就是一個不錯的例子,當自己的顏色發生變化的時候,也要通知自己的所有子控件:
procedure TWinControl.CMColorChanged(var Message: TMessage); begin inherited; // 自己的顏色變化起效果,是通過這句話來實現的,它包含了一連串的執行過程。父控件變化完了再通知子控件,風格很強勢。 FBrush.Color := FColor; NotifyControls(CM_PARENTCOLORCHANGED); // 第二個消息,組建消息並傳播,通知子控件,但沒有任何子控件響應 end;
再對比一下TWinControl.CMInvalidate,就會發現父控件先做變化,后通知子控件(強勢);而子控件必須先通知父控件,后做變化(弱勢):
procedure TWinControl.CMInvalidate(var Message: TMessage); var I: Integer; begin if HandleAllocated then begin if Parent <> nil then Parent.Perform(CM_INVALIDATE, 1, 0); // 第四個消息,遞歸,先通知父類(父類要提前通知)。Form1的Parent是Application,它沒有響應。 if Message.WParam = 0 then begin // API,第二個參數為NULL的話,則重畫整個客戶區;第三個參數TRUE則背景重繪。 InvalidateRect(FHandle, nil, not (csOpaque in ControlStyle)); // 總算初步達到目的,使Form1失效,后面還要自繪 { Invalidate child windows which use the parentbackground when themed } if ThemeServices.ThemesEnabled then for I := 0 to ControlCount - 1 do if csParentBackground in Controls[I].ControlStyle then // important Controls[I].Invalidate; end; end; end;
但是無論父控件通知子控件,還是子控件通知父控件,都僅僅是通知而已,都不能強行阻止對方執行自己的變化過程(控件人格獨立)。
--------------------------------------------------------------------------
TControl對TWinControl的區別和聯系,主要是TControl的顯示完全要依賴於TWinControl控件,主要代碼在這里(紅色字體):
procedure TControl.Update; begin if Parent <> nil then Parent.Update; // 圖形控件沒法刷新自己,讓父控件去刷新 end; procedure TControl.InvalidateControl(IsVisible, IsOpaque: Boolean); 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)); end; end; 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;
--------------------------------------------------------------------------
總結:
TControl有兩個作用(通用鼠標消息和文字顏色屬性功能):第一是定義所有圖形控件和Win控件都要用到的通用功能(比如10個鼠標按鈕消息),第二是定義所有圖形控件都要用到的通用功能,比如InvalidateControl,SetColor,GetText,SetText等等,數不勝數
TWinControl兩個作用(管理子控件功能和調用句柄API的功能):第一是定義Windows句柄控件所要用到的通用功能(使用API真正起作用),第二是管理其圖形子控件的顯示,很多時候也得管理其WinControl(如果Windows沒有很好的直接管理它們的話)。
--------------------------------------------------------------------------
這里對TControl和TWinControl的同名覆蓋函數總結了一下,並列個表對比它們的功能和實現手法:
http://www.cnblogs.com/findumars/p/4153769.html