Delphi控件开发基础篇

Delphi控件开发基础篇


Delphi控件开发基础篇
Delphi中控件(Component)是依托于 VCL(Visual Component Library)框架而出的。其根类是TComponent。
大凡控件都是从如下几个类继承得来。
TGraphicControl
在实际的界面开发工作中,有许多简单的控件,其本身仅仅是简单的显示一些提示信息,不会完成什么复杂的工作,譬如 TLabel,TPaintBox等控件;而对于win32开发来说,窗口句柄是有限且宝贵的资源,其创建,使用及销毁 成本太高。所以需要一种无句柄的操作模式,且可以自绘的能力, TGraphicControl完全满足这一点。
TGraphicControl的自绘能力由FCanvas负责。并且由WMPaint消息函数调用。各位读者可能会有疑问,TGraphicControl并非Windows窗口类,它怎么能处理消息呢,关于这一点,就不得不提VCL的变态之处,从TComponent 中提供了一套控件自身的消息传递机制—Perform。它扮演者类似于Windows的消息分发函数 DispatchMessage的角色。 最后的绘制工作,落到Paint函数头上,这是个虚拟函数(也算是抽象函数,此处使用了 Spacehold 占位符技术),所以 WMPaint和Paint提供了绘制框架, 此类控件的具体绘制工作,是由后代的Paint函数负责。下面查看一下 TPaintBox的代码,验证一下我们的猜测。


    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;
    
    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;

通过代码可以看出,TPaintBox控件很简单,仅仅提供了Paint函数,以及由它调用供使用者自定义绘制的OnPaint函数。提供了外部绘制接口。
这类组件不能成为其他控件的父类。
这类控件的绘制依赖父类画布能力。受父类影响很重。

TPaintBox = class(TGraphicControl)
  private
    FOnPaint: TNotifyEvent;
  protected
    procedure Paint; override;
  public
    constructor Create(AOwner: TComponent); override;
    property Canvas;
  published
    property Align;
     ... {把父类的属性 发布出去}
    property OnPaint: TNotifyEvent read FOnPaint write FOnPaint;
  end;


constructor TPaintBox.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  ControlStyle := ControlStyle + [csReplicatable];
  Width := 105;
  Height := 105;
end;

procedure TPaintBox.Paint;
begin
  Canvas.Font := Font;
  Canvas.Brush.Color := Color;
  if csDesigning in ComponentState then
    with Canvas do
    begin
      Pen.Style := psDash;
      Brush.Style := bsClear;
      Rectangle(0, 0, Width, Height);
    end;
  if Assigned(FOnPaint) then FOnPaint(Self);
end;

TWinControl
VCL中所有封装Windows控件的组件 都是从这个类中继承而来,并最终的绘制都有Windows来完成,这些组件只是提供一个封装转化接口,以适应Delphi使用形式,扩充了使用外延。所以它不带有Canvas属性和 Paint方法。比如 TButton,TEdit等控件都是如此。且这些组件的代码位于 StdCtrls单元。
TCustomControl
那么如果我既想要Windows帮我绘制,还得自绘一些东西,算是定制化的window控件,该怎么办呢,那叫让它从TCustomControl继承吧。它既能让Windows帮你完成基本绘制,也可以自己在此基础上个性化绘制。比如 TPanel组件就是从此继承得来。并且界面刷新及时。

其代码和TGraphicControl,及其类似。只不过他还提供PaintWindow函数。以及知名csCustomPaint属性。

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;

{ TCustomControl }

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

destructor TCustomControl.Destroy;
begin
  FCanvas.Free;
  inherited Destroy;
end;

procedure TCustomControl.WMPaint(var Message: TWMPaint);
begin
  Include(FControlState, csCustomPaint);
  inherited;
  Exclude(FControlState, csCustomPaint);
end;

procedure TCustomControl.PaintWindow(DC: HDC);
begin
  FCanvas.Lock;
  try
    FCanvas.Handle := DC;
    try
      TControlCanvas(FCanvas).UpdateTextFlags;
      Paint;
    finally
      FCanvas.Handle := 0;
    end;
  finally
    FCanvas.Unlock;
  end;
end;

procedure TCustomControl.Paint;
begin
end;

TComponent
前面讲了那么多,但是都是可视化组件,但是还有一类组件,视为非可视化组件,他们不涉及界面交互方面的事,典型的有TTimer等。所以他们从TComponent 继承 即可。

TTimer = class(TComponent)
  private
    FInterval: Cardinal;
    FWindowHandle: HWND;
    FOnTimer: TNotifyEvent;
    FEnabled: Boolean;
    procedure UpdateTimer;
    procedure SetEnabled(Value: Boolean);
    procedure SetInterval(Value: Cardinal);
    procedure SetOnTimer(Value: TNotifyEvent);
    procedure WndProc(var Msg: TMessage);
  protected
    procedure Timer; dynamic;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
  published
    property Enabled: Boolean read FEnabled write SetEnabled default True;
    property Interval: Cardinal read FInterval write SetInterval default 1000;
    property OnTimer: TNotifyEvent read FOnTimer write SetOnTimer;
  end;