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事件。
Delphi控件开发浅入深出(五)
五、复合控件
复合控件是Delphi控件中非常重要的一种控件,复合控件就是将两个或两个以上的控件重新组合成一个新的控件。例如TspinEdit、TlabeledEdit、TDBNavigator等就是复合控件,TDBNavigator其实就是在一个Panel放上若干个Button而已。制作一个复合控件时,我们一般从TwinControl派生控件。
我们这次做的控件是拥有一个Edit编辑框和一个Button按钮的复合控件,在用户在编辑框中输入文字的过程中,Button将随时显示编辑框中文字的长度。我们把控件的源码先展示给大家。
unit EditButton; interface uses
SysUtils, Classes, Controls, StdCtrls, Messages; type
TEditButton = class(TWinControl) private
FEdit: TEdit; FButton: TButton; FText: string;
procedure FSetText(AValue: string);
procedure OnEditChange(Sender: TObject); protected
procedure WMSize(var Msg: TMessage);message WM_SIZE; public
constructor Create(AOwner: TComponent);override; destructor Destroy;override; published
property Text: string read FText write FSetText; end;
procedure Register; implementation
procedure Register; begin
RegisterComponents('Linco', [TEditButton]);