Delphi控件开发浅入深出 二 三

二、控件开发纵览

  通过开发上边这个控件,我们已经对Delphi控件开发有了基本的认识。下面我们将系统的讲述一下控件开发的知识。

制作控件第一件事就是选择适当的 Delphi对象类型作为父对象,以派生新的对象。子对象可以继承父对象的全部非 private部件,但不能摆脱不需要的部件。因此,所选父对象应尽可能多地包含子对象所需的属性、事件和方法,但不应包含子对象不需要的东西。Delphi必须从Tcomponent或Tcomponent的子类派生。TComponent是所有 Delphi控件的基点,但若直接从 TComponent 派生新控件,很多东西就需要自己从头做起。一般只有非可视控件才直接从 TComponent派生。 Delphi提供了若干专门用于制作控件(可视控件)的对象类型,都是从 TControl和 TWinControl派生而来。

TControl的子类型用于非窗口式控件, TWinControl的子类型则用于窗口式控件。除非特殊需要,一般不直接从 TControl和TWinControl派生新控件,而是从其子类型派生。这样可以充分利用原有的属性、事件和方法,减少很多工作量。在这些控件类型中,非通用的属性、事件和方法都声明为 protected。这样可以禁止控件用户访问,又能被子类型继承和修改。在新控件中,可以简单地把继承来的属性和事件重新声明为 published,使控件用户能在设计期通过对象编辑窗口访问,也可以进而修改属性的默认值和读写方式,或是重载( override)事件处理子过程和其他控件方法,以修改其中的程序代码。重声明可以放宽访问权限,但不能相反,例如,不可能把 published属性重声明为 private或 protected。

Delphi控件也是Delphi的类,所有的控件都有特定的结构。一般控件包括三大组成部分:属性、方法和事件,下面先介绍初学控件开发的最难懂的属性部分,其他部分我们将在以后章节为大家介绍。

属性主要部分就是属性的读写方法(或读写字段)。前面的例子用的是读写字段,也就是对属性的读写都通过对字段的读写来完成。下面为大家讲解一下读写方法的使用方法:

TmyComponent = class(TComponent)

Private

  Fcount: Integer;

  Procedure SetCount(Avalue: Integer);

Pulbished

  Property Count: Integer read Fcount write SetCount;

End;

这个例子中当执行MyComponent1.Count := 1;这样的代码时,将会导致SetCount方法执行,并且参数Avalue被指定为1;当执行 I := MyComponent1.Count;方法时,会将 Fcount的值返回给I。

属性的声明语法允许属性声明的Read和Write部分用访问方法取代对象私有数据域。属性的读方法是不带参数的函数,返回同属性相同类型的值。通常读方法以Get开头。属性的写方法总是带一个参数的过程。写方法常常以Set开头。

三、开关控件TlincoSwitch
用过Delphi1(好古老的东东呀!)的人相信都记得这个开关控件 r_delphicomdev3-1.jpg,不知道当初Borland为什么把这么一个在开发普通应用程序中应用不到的工控控件放到Delphi中,而且在Delphi2及其以后的版本中再也没有见过它的身影。让我们怀着怀旧的心情把这位“开国元老”请出来吧!

1、建立位图资源文件:

用Image Editor建立一个Res文件,并在文件中分别建立下面两个位图r_delphicomdev3-1.jpgr_delphicomdev3-2.jpg,并分别命名为SWITCHON、SWITCHOFF。保存此Res到控件单元所在目录下。

2、写控件代码。

unit LincoSwitch;

interface

uses

  SysUtils, Classes, Controls, Graphics, Windows;

type

  TLincoSwitch = class(TCustomControl)

  private

    FIsOn: Boolean;

    FPicOn: Graphics.TBitmap;

    FPicOff: Graphics.TBitmap;

    procedure FSetIsOn(AValue: Boolean);

  protected

    procedure Click;override;

    procedure Paint;override;

  public

    constructor Create(AOwner: TComponent);override;

    destructor Destroy;override;

  published

    property IsOn: Boolean read FIsOn write FSetIsOn;

    property OnClick;

    property OnKeyDown;

    property OnKeyPress;

    property OnKeyUp;

    property OnCanResize;

    property OnDblClick;

    property OnMouseDown;

    property OnMouseMove;

    property OnMouseUp;

    property OnMouseWheel;

    property OnResize;

  end;

procedure Register;

implementation

{$R *.res}

procedure LoadBitmapFromRes(ABitmapId: string; ABitmap: Graphics.TBitmap);

begin

  ABitmap.LoadFromResourceName(hInstance, ABitmapId);//从资源文件中读取位图

end;

constructor TLincoSwitch.Create(AOwner: TComponent);

begin

  inherited Create(AOwner);

  FPicOn := Graphics.TBitmap.Create;

  FPicOff := Graphics.TBitmap.Create;

  LoadBitmapFromRes('SWITCHON', FPicOn);

  LoadBitmapFromRes('SWITCHOFF', FPicOff);

  Invalidate;

end;

destructor TLincoSwitch.Destroy;

begin

  FPicOn.Free;

  FPicOff.Free;

  inherited;

end;

procedure TLincoSwitch.Click;

begin

  IsOn := not IsOn;//改变按钮的状态

  Invalidate;

  inherited;

end;

procedure TLincoSwitch.Paint;

begin

//画开关图案  

if IsOn then

    

      StretchBlt(Canvas.Handle, 0, 0, self.Width, self.Height, FPicOn.Canvas.Handle,

             0, 0, FPicOn.Width, FPicOn.Height,SRCCOPY)

  else

      StretchBlt(Canvas.Handle, 0, 0, self.Width, self.Height, FPicOff.Canvas.Handle,

             0, 0, FPicOff.Width, FPicOff.Height,SRCCOPY);

end;

procedure TLincoSwitch.FSetIsOn(AValue: Boolean);

begin

  FIson := AValue;

  Invalidate;

end;

procedure Register;

begin

  RegisterComponents('Linco', [TLincoSwitch]);

end;

end.

3、代码分析

  (1)、因为我们要在控件表面上将按钮的图案画出来,所以我们选择TcustomControl做为父类控件,因为它有个Canvas属性,我们可以利用Canvas在控件表面作图。不选用Tcontrol的原因是因为它有很多我们不需要的属性。

(2)、ABitmap.LoadFromResourceName(hInstance, ABitmapId);是从资源文件中读取Id为AbitmapId的位图,关于资源文件的使用请参考其他相关资料。注意代码中的“{$R *.res}”,它的作用是将资源文件编译到程序文件中,如果没有这个预编译条件,程序将会出现错误。

(3)、StretchBlt是将位图画到画板上,使用方法请参考MSDN。

(4)、我们为控件增加了IsOn属性。这个布尔属性用来表示开关的状态(开/关)。

从property IsOn: Boolean read FIsOn write FSetIsOn;我们可以看出这个属性是个可读可写的属性。当读这个属性时会将FisOn的值返回给调用者,而写属性时则会调用FsetIsOn方法,并将赋给属性的值做为参数传递给FsetIsOn。在FsetIsOn方法中,有如下实现代码:

  FIson := AValue;

  Invalidate;

首先将Fison设置为参数传递来的值,然后调用  Invalidate;要求重画控件,以告诉用户控件的状态已经改变,这一点是使用写字段无法做到的。

(5)    

    FPicOn: Graphics.TBitmap;

FPicOff: Graphics.TBitmap;

是声明两个.Tbitmap类型变量以保存控件的开关两种状态的图案。

(6)

    procedure Click;override;

procedure Paint;override;

分别是覆盖父类中相应的调度方法。当控件被鼠标单击时,Click方法会被调用,我们将在Click中改变控件的开关状态;Paint方法则在用户调用  Invalidate方法或控件发生重画时调用,我们一般在这个方法绘制控件的图案。

(7)、TcustomControl中又很多事件处理句柄。比如OnClick、OnKeyDown等,但是它把他们声明成了Protected保护级别,所以我们在Object Inspector中看不到他们,如果我们要他们可以在Object Inspector中被用户编辑的话,只要在Published中重新声明他们即可,不用写他们的读写方法,只要使用:Property  属性名;

这样的方法就可以。比如这个例子中的:Property Onclick;

思考题:

1、        做一个有特效的按钮控件,当鼠标按下时按钮是一个红色边框的空心圆,当鼠标松开时按钮是一个淡绿色边框的空心圆。

 

2、        对于父类控件中为protected的属性,如果想将它在子类控件中公布,应该怎么做?请思考Delphi为什么要将一些属性设为protected级别?

 

Delphi控件开发浅入深出(三)

四、对特定字符串敏感的Edit控件

我们这个控件将演示控件的自定义事件的书写。这个控件有一个类型为string的SensitiveText属性,当用户在输入框中输入的文字为InvalidText时就会触发OnSensitiveText事件。按照惯例,我先把源码展示给大家:

unit TextSenseEdit;

interface

uses

  SysUtils, Classes, Controls, StdCtrls;

type

  TSensitiveTextEvent = procedure(AText: string) of object;//方法指针

  TTextSenseEdit = class(TEdit)

  private

    FSensitiveText: string;

    FOnSensitiveText: TSensitiveTextEvent;

    procedure SetSensitiveText(AValue: string);

  protected

    procedure Change;override;

  public

  published

    property SensitiveText: string read FSensitiveText write SetSensitiveText;

    property OnSensitiveText: TSensitiveTextEvent read FOnSensitiveText write FOnSensitiveText;

  end;

procedure Register;

implementation

procedure Register;

begin

  RegisterComponents('Linco', [TTextSenseEdit]);

end;

procedure TTextSenseEdit.Change;

begin

  inherited;

  if Text = SensitiveText then

    if Assigned(OnSensitiveText) then

      OnSensitiveText(Text);

end;

procedure TTextSenseEdit.SetSensitiveText(AValue: string);

begin

  FSensitiveText := AValue;

end;

end.

代码解释:

(1)、SensitiveText属性的添加方法大家已经熟悉了,这里不多解释。

(2)、正如大家猜测的,Change方法正是编辑框文字发生变化时的调度方法,它将引起OnChange事件。我们可以在这个方法中监控编辑框文字发生的变化,当文字等于SensitiveText就触发OnSensitiveText事件(具体的实现方法在后边解释)。

(3)、Delphi中的控件的事件机制是通过方法指针来实现的。声明方法指针的格式为:

方法指针名称 = procedure(参数列表) of object;

声明事件属性的方法与声明普通属性的方法相同。在我们这个例子中,我们首先声明一个FOnSensitiveText: TSensitiveTextEvent;私有变量,然后property OnSensitiveText: TSensitiveTextEvent read FOnSensitiveText write FOnSensitiveText; 声明事件属性。这样注册控件后,当用户把控件放到窗体中后,就会在Object Inspector中Evnets页中出现OnSensitiveText事件,我们就可以像使用其他事件一样使用这个事件了。

  但是我们现在只是声明了一个事件属性,并没有书写任何代码来激发这个事件。我们应该在合适的时候激发此事件,显而易见我们应该在Change方法中激发此事件:

procedure TTextSenseEdit.Change;

begin

  inherited;

  if Text = SensitiveText then

    if Assigned(OnSensitiveText) then

      OnSensitiveText(Text);

end;

当if Text = SensitiveText时就判断控件使用者是否为OnSetSensitiveText写代码了(准确的说是是否为OnSetSensitiveText事件句柄赋值了),如果写代码了则调用OnSetSensitiveText(Text);来激发OnSetSensitiveText事件,并把控件的Text传递给方法的Avalue参数。正如“方法指针”这个名字一样,被声明为方法指针类型的变量可以当作方法使用,用来激发事件。VCL已经为我们预定义了一些常用的事件句柄,我们直接拿来使用:TnotifyEvent,TmouseEvent,TmouseMoveEvent,TkeyPressEvent等,具体可以参考VCL源码。

思考题:

1、做一个支持累加运算的文本编辑框控件,用户可以在编辑框中输入正整数。当用户按回车时,如果编辑框中输入的不是正整数(为负数、小数或一般字符串)则触发控件的OnError事件;如果输入的是正整数,则开始计算从1到用户输入的那个正整数中所有整数的和(用1+2+3+……这种累加的办法实现,不要用(1+n)*n/2这种直接计算的方法),并且在计算工程中如果发现计算的中间结果位数是5,则触发OnTailFive事件。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值