對TControl和TWinControl相同與不同之處的深刻理解(每一個WinControl就相當於扮演了整個Windows的窗口管理角色,主要是窗口顯示和窗口大小)——TWinControl就兩個作用(管理子控件的功能和調用句柄API的功能)


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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM