核心庫類之TGraphicControl/TcustomControl 與畫布(Canvas)

1.TGraphicControl/TcustomControl 與畫布(Canvas)
    VCL中,TCotnrol之下的組件分兩條路各行其道。一條爲圖形組件,這類組件並非窗口,職責只在於顯示圖形、圖像,其基類是TGraphicControl;另一條爲窗口組件,這類組件本身是一個Windows窗口(有窗口句柄),其基類是TWinControl。
    TGraphicControl作爲顯示圖形、圖像的組件分支,從其開始就提供了一個TCanvas類型的Canvas屬性,以便在組件上繪製圖形、顯示圖像。
    對於窗口組件的分支,TWinControl並沒有提供Canvas屬性,而在其派生類TCustomControl纔開始提供Canvas屬性。

                       控件類分支
    TGraphicControl與TCustomControl的實現都在Controls.pas單元中,它們的聲明看上去也是如此相似:

  TGraphicControl = class(TControl)
  
private
    FCanvas: TCanvas;
    procedure WMPaint(var Message: TWMPaint); message WM_PAINT;
  protected
    procedure Paint; virtual;
    
property Canvas: TCanvas read FCanvas;
  
public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
  
end;

  TCustomControl 
= class(TWinControl)
  
private
    FCanvas: TCanvas;
    procedure WMPaint(var Message: TWMPaint); message WM_PAINT;
  protected
    procedure Paint; virtual;
    procedure PaintWindow(DC: HDC); override;
    
property Canvas: TCanvas read FCanvas;
  
public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
  
end;

       它們提供了Canvas屬性,只不過此時Canvas屬性被隱藏在protected節中,它們的派生類可以選擇性地將其publish。
由於TGraphicControl與TCustomControl在有關Canvas熟悉的實現上也非常相似,在此只以TGraphicControl的實現來講解“畫布”屬性。
由TGraphicControl的聲明中的
property Canvas: TCanvas read FCanvas;
可知Canvas是一個只讀屬性,其載體是private的成員對象FCanvas。FCanvas在TGraphicControl的構造函數中被創建:

{ TGraphicControl }

constructor TGraphicControl.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FCanvas :
= TControlCanvas.Create;
  TControlCanvas(FCanvas).Control :
= Self;
end;

destructor TGraphicControl.Destroy;
begin
  
if CaptureControl = Self then SetCaptureControl(nil);
  FCanvas.Free;
  inherited Destroy;
end;

procedure TGraphicControl.WMPaint(var Message: TWMPaint);
begin
  
if Message.DC <> 0 then
  begin
    Canvas.Lock;
    try
      Canvas.Handle :
= Message.DC;
      try
        Paint;
      finally
        Canvas.Handle :
= 0;
      
end;
    finally
      Canvas.Unlock;
    
end;
  
end;
end;

procedure TGraphicControl.Paint;
begin
end;


       在此需要注意的是,FCanvas在聲明時,是被聲明爲TCanvas類型的,而在創建時,卻創建了TControlCanvas的示例。其實,TControlCanvas是TCanvas的派生類,它提供了一些額外的屬性和事件來輔助在Control(控件)上提供“畫布”屬性。
這裏暫停一下,先來看一下TcontrolCanvas:

  TControlCanvas = class(TCanvas)
  
private
    FControl: TControl;
    FDeviceContext: HDC;
    FWindowHandle: HWnd;
    procedure SetControl(AControl: TControl);
  protected
    procedure CreateHandle; override;
  
public
    destructor Destroy; override;
    procedure FreeHandle;
    procedure UpdateTextFlags;
    
property Control: TControl read FControl write SetControl;
  
end;

       TControlCanvas將Canvas綁定到一個TControl實例上,其內部的FControl指針即指向Canvas所屬的TControl實例。
      TCanvas提供了一個空的虛方法CreateHandle()。這個虛方法在TControlCanvas中被覆蓋重新實現:

procedure TControlCanvas.CreateHandle;
begin
  
if FControl = nil then inherited CreateHandle else
  begin
    
if FDeviceContext = 0 then
    begin
      
with CanvasList.LockList do
      try
        
if Count >= CanvasListCacheSize then FreeDeviceContext;
        FDeviceContext :
= FControl.GetDeviceContext(FWindowHandle);
        Add(Self);
      finally
        CanvasList.UnlockList;
      
end;
    
end;
    Handle :
= FDeviceContext;
    UpdateTextFlags;
  
end;
end;


在CreateHandle()方法中,如果FControl是TWinControl或其派生類的實例,即控件本身是窗口,則取得該窗口的設備上下文句柄賦給Handle屬性;如果FControl非TWinControl或其派生類的實例,即控件本身並非窗口,則將其父窗口的設備上下文句柄賦給Handle。這些都是通過TControl聲明的虛函數GetDeviceContext()實現的,因爲TWinControl覆蓋重新實現了GetDeviceContext()。
說完TControlCanvas,下面繼續剛纔的話題。TGraphicControl的構造函數中創建了TControlCanvas實例並賦給FCanvas。構造函數的最後一行代碼
TControlCanvas(FCanvas).Control := Self;
將Canvas屬性綁定到了控件本身。
然後,TGraphicControl定義了一個處理WM_PAINT Windows消息的消息處理函數:
procedure WMPaint(var Message: TWMPaint); message WM_PAINT;
在WMPaint()方法中,根據接受到的消息的參數所給出的窗口的設備上下文句柄,給Canvas屬性的Handle重新賦值,並且調用虛方法Paint():

procedure TGraphicControl.WMPaint(var Message: TWMPaint);
begin
  
if Message.DC <> 0 then
  begin
    Canvas.Lock;
    try
      Canvas.Handle :
= Message.DC;
      try
        Paint;
      finally
        Canvas.Handle :
= 0;
      
end;
    finally
      Canvas.Unlock;
    
end;
  
end;
end;


虛方法Paint()可以被TGraphicCotnrol的派生類所覆蓋,重新定義並實現繪製圖形、圖像的方法,並且TGraphicControl的派生的實例總是可以放心使用其Canvas屬性,而不必自行獲得窗口的設備上下文句柄。而虛方法Paint()在TGraphicControl中的實現也只是一個空方法而已。

2.TCustomPanel與窗口重繪
TCustomPanel派生自TCustomControl,是所有Panel類組件的基類。TCustomPanel與4.8節中所述的TGraphicControl非常類似,只是TCustomControl派生自TWinControl,所以它的實例是一個窗口。
TCustomControl與TGraphicControl一樣,擁有一個空的虛方法Paint(),以便讓派生類決定如何重繪窗口。
現在就來看一下TcustomPanel。它從TCustomControl派生,並且覆蓋重新實現了Paint()方法。在此,我們不關心TCustomPanel所實現的其他特性,而只關注其實現的Paint()方法。TCustomPanel實現的Paint()方法負責將組件窗口繪製出一個Panel效果(邊框、背景和標題)。先來看一下Paint()方法:

procedure TCustomPanel.Paint;
const
  Alignments: 
array[TAlignment] of Longint = (DT_LEFT, DT_RIGHT, DT_CENTER);
var
  Rect: TRect;
  TopColor, BottomColor: TColor;
  FontHeight: 
Integer;
  Flags: Longint;

  procedure AdjustColors(Bevel: TPanelBevel);
  begin
    TopColor :
= clBtnHighlight;
    
if Bevel = bvLowered then TopColor := clBtnShadow;
    BottomColor :
= clBtnShadow;
    
if Bevel = bvLowered then BottomColor := clBtnHighlight;
  
end;

begin
  Rect :
= GetClientRect;
      // 畫邊框
  
if BevelOuter <> bvNone then
  begin
    AdjustColors(BevelOuter);
    Frame3D(Canvas, Rect, TopColor, BottomColor, BevelWidth);
  
end;
  Frame3D(Canvas, Rect, Color, Color, BorderWidth);
  
if BevelInner <> bvNone then
  begin
    AdjustColors(BevelInner);
    Frame3D(Canvas, Rect, TopColor, BottomColor, BevelWidth);
  
end;
  
with Canvas do
  begin
        // 畫背景
    Brush.Color :
= Color;
    FillRect(Rect);
    Brush.Style :
= bsClear;
        // 寫標題
    Font :
= Self.Font;
    FontHeight :
= TextHeight('W');
    with Rect do
    begin
      Top :
= ((Bottom + Top) - FontHeight) div 2;
      Bottom :
= Top + FontHeight;
    
end;
    Flags :
= DT_EXPANDTABS or DT_VCENTER or Alignments[FAlignment];
    Flags :
= DrawTextBiDiModeFlags(Flags);
    DrawText(Handle, PChar(Caption), 
-1, Rect, Flags);
  
end;
end;


Paint()方法含有一個內嵌函數AdjustColors(),其作用是確定邊框的上下線條顏色(一條邊框由兩個像素寬度的直線構成,形成立體效果)。
TCustomPanel使用其基類(TCustomControl)提供的Canvas屬性,覆蓋其基類定義的虛方法Paint(),完成了窗口重繪過程。
在自己編寫組件時,如果需要在組件表面繪製圖形、圖像的話,就可以如同TCustomPanel一樣,覆蓋重新實現Paint()方法。同時,使用基類提供的Canvas屬性,對於繪圖過程來說,也是非常簡單的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章