细说制作VCL 组件
洪高刚
2023-12-01
制作组件,是Delphi应用中最激动人心也是最有趣的内容之一,程序设计者制作组件的目的之一,是把大量的重复劳动用组件的方法定制起来,加快软件开发的效率。当我们向Delphi添加一个新的软件包时,实际上就是使用了一个新的类扩展了VCL ,这个新类从一个已有组件相关的类中派生出来,并向继承它的类添加了新的功能。
VCL(Visual Component Library)构件,是Delphi的核心,制作这样的构件,主要采取这样几个步骤:
1〉选择合适的基类进行继承
构件的基类要根据需要选择,例如:
TComponent 是完全空白的基类;
TControl 是可视类的基类;
TWinControl 是窗体类的基类;
TGraphControl 是图形类的基类。
等等。
2〉加入构件的属性、方法和事件
属性方法和事件,大部分可以来自于基类的继承,但也可以新定义。可以用覆盖基类的方法,实现新的功能。新定义的方法,也可以设置成抽象的、动态的或虚拟的。
事件也可以从基类继承,也可以自己定义事件,这种事件,可以由Windows消息触发(如点击鼠标),也可以自己定义由内部状态的变化来触发。
3〉注册构件
如果希望构件是可视化的,就需要注册,方法是:
Components -> Install Components
下面先举一个简单的例子。
一、制作第一个组件
首先制作一个组件,了解一下组件制作的方法,目标是一个下拉列表框,显示所有的系统字体:
首先要清空原来的Form。
然后
Component(组件)---New Component--启动Component Wizard
或File--New--Other--Component(黄色齿轮的图标)也可以启动Component Wizard。
Component Wizard需要如下信息:
Ancestor type:我们想继承的父类名称(这里为TComboBox);
注意一下,一般同名的基类,Q开头的是CLX构件,
另一个是VCL构件。
Class Name:正在建立的组建类名称(这里为TMd4FontCombo);
Palette Page:组件放置的页面(这里建立的一个新的页面Md4);
Unit File Name:放置源代码的文件名(这里为Md4FontBox)
最后为当前搜索路径,请使用默认值。
Ok (另一个Install是立刻在组建包中安装组件)
这就得到了一个简单的Pascal源文件:
------------------------------------------------------------------
unit Md4FontCombo;
interface
uses
Windows, Messages, SysUtils, Classes, Controls, StdCtrls;
type
TMd4FontCombo = class(TComboBox)
private
{ Private declarations }
protected
{ Protected declarations }
public
{ Public declarations }
published
{ Published declarations }
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents(‘Md4‘, [TMd4FontCombo]);
end;
end.
---------------------------------------------------------------
这个文件的一个关键元素是类定义:
TMd4FontCombo = class(TComboBox)
它说明了父类开始。
和一般单元相比,它多了一个Register过程,这里默认定义了一个用于注册组件的过程Register:
RegisterComponents(‘Md4‘, [TMd4FontCombo]);
说明了把TMd4FontCombo注册到Md4页。
需注意的是Register过程中的首字符应该大写,这主要是为了和C++ Builder 兼容。还要注意的是,当建立组件的时候应该使用一个命名规则,大多数Delphi开发人员,都使用两到三个字母标记,作为他们开发的组件前缀,比如这里用Md4。
还需要注意一下Uses的变化:
uses
Windows, Messages, SysUtils, Classes,
forms,Controls, StdCtrls;
这里多了一个forms,因为后面要用到Screen,如果这里没有声明Forms,将会编译报错。
在interface作如下声明:
public
{ Public declarations }
//下面两行为自己添加的构造函数
Constructor Create(Aowner:TComponent);override;
procedure CreateWnd;override;
published
{ Published declarations }
{下面两行自己添加,为设置组件的属性,没有设置的将从父类继承过来默认值}
Property Style default csDropDownList;
Property Items stored false;
在implementation区域写上具体的程序:
Constructor TMd4FontCombo.Create(Aowner:TComponent);
begin
inherited Create (Aowner);
style:=csDropDownList;
end;
procedure TMd4FontCombo.CreateWnd;
begin
inherited CreateWnd;
{把系统字体添加进组合框,注意uses中要加上Forms,否则Screen由于不被声明而出错}
Items.Assign (Screen.Fonts);
end;
end.
这里需要指明的是,表面上看,为了在组合框里面添加所有的系统字体,只要在重载Create对象方法中添加语句:
Items:=Screen.Fonts;
就可以了,实际上这是行不通的。这是因为在组件完全建立起来以前,我们并不能访问组合框的Items属性。
所以,我们在CreateWnd过程中才给组合框赋新的字符串。
还要注意的是,除了在Create对象方法中赋予组件的 style属性一个新值以外,还要用default关键字通过设置一个值来重新定义属性:
Property Style default csDropDownList;
这两步操作是必须的,因为向属性声明添加default关键字,不会对属性的初始值产生直接的影响。包含缺省值的属性不用包含在窗体定义中(包括文本描述),default 关键字将向流代码说明,组件初始化代码将设置该属性的值。
另一个重新定义的属性Items被设置成根本就不保存在DFM文件中的属性:
Property Items stored false;
因为程序启动时将从新装入该值,保存是没有意义的。
下面是完整的源程序:
---------------------------------------------------------
unit QMd4FontCombo;
interface
uses
Windows, Messages, SysUtils, Classes, forms,Controls, StdCtrls;
type
TMd4FontCombo = class(TComboBox)
private
{ Private declarations }
protected
{ Protected declarations }
public
{ Public declarations }
//下面两行为自己添加的构造函数
Constructor Create(Aowner:TComponent);override;
procedure CreateWnd;override;
published
{ Published declarations }
//下面两行为自己添加
Property Style default csDropDownList;
Property Items stored false;
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents(‘Md4‘, [TMd4FontCombo]);
end;
Constructor TMd4FontCombo.Create(Aowner:TComponent);
begin
inherited Create (Aowner);
style:=csDropDownList;
end;
procedure TMd4FontCombo.CreateWnd;
begin
inherited CreateWnd;
//注意uses中要加上Forms,否则Screen不被声明
Items.Assign (Screen.Fonts);
end;
end.
--------------------------------------------------------
二、建立组件包
Component--- install Component
在对话框中第一项Unit file Name,是组件单元.pas文件的名字。其他只要按缺省OK,就可以了。
这是我们可以看到,在控件选项卡多了一个Md4,里面多了一个控件,这就是我们制作的控件TMd4FontCombo。
三、给组件以新的位图
在没有给组件位图的时候,组件默认的是以其父类的位图为其代表的,你也可以给组件一个新的位图。
方法如下:
Tools---Image editor 调出位图编辑器
New-- Component Resource File (.dcr),dcr(Delphi Component Resource)是带有不同扩展名的RES文件。
在弹出的窗口中,右键---New--Bitmap--24*24--OK
它默认的给了一个项目名称Bitmap1,现在你必须改成和当前正在构造的控件类相同(前面要加T),同时全部大写,这里为:
TMD4FONTCOMBO
这是Delphi的强制要求。
双击可以绘图,保存的名字也要与项目匹配,这里为:
MD4FONTCOMBO.DCR
这样,在install Component就可以看到新的图标了,如果不行,程序中可以在plementation后面加上编译开关,强行加入这个图标文件:
{$R Md4FontCombo.dcr}
四、更复杂的例子
下面我们定义一个组件,从TGraphicControl为父类继承来一个SampleShape,来画一些标准的图形。请仔细阅读这个程序,并自己建立一个类似的组件。
首先要清空原来的Form。
然后
Component(组件)---New Component--启动Component Wizard
或File--New--Other--Component(黄色齿轮的图标)
Component Wizard需要如下信息:
Ancestor type:我们想继承的父类名称(这里为TGraphicControl);
注意一下,一般同名的基类,Q开头的是CLX构件,
另一个是VCL构件。
Class Name:正在建立的组建类名称(这里为TSampleShape);
这就建造了一个基本的框架:
-------------------------------------------------------------------
unit SampleShape;
interface
uses
Windows, Messages, SysUtils, Classes, Controls;
type
TSampleShape = class(TGraphicControl)
private
{ Private declarations }
protected
{ Protected declarations }
public
{ Public declarations }
published
{ Published declarations }
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents(‘Samples‘, [TSampleShape]);
end;
end.
-----------------------------------------------------------------
写程序,注意一下把Graphics加入到Uses中去。
----------------------------------------------------------------
unit SampleShape;
interface
uses
Windows, Messages, SysUtils, Classes, Controls,Graphics;
type
//声明FShape的属性用枚举,这样,在属性栏里希望有枚举的表述
//这里只是声明了一个枚举的类型TSampleShapeType
TSampleShapeType=(sstRectangle,sstSquare,sstRoundRect,
sstRoundSquare,sstEllipse,sstcircle);
//TSampleShape是由TGraphicControl派生出的
TSampleShape = class(TGraphicControl)
private
{ Private declarations }
//声明一个属性时,声明私有域保护属性值,在下面描述读写属性值的方法
//拥有的每一个对象必须有对象域的声明,该域在部件存在时总指向Owned对象。
//通常,部件在constructor中创建它,在destructor中撤消它。
//Owned对象的域总是定义为私有的,如果要使用户或其它部件访问该域,通常要提供访问属性。
FShape:TsampleShapeType;
procedure SetShape(value:TSampleShapeType);
// 使用Canvas的Pen和Brush给TPen和TBrush设置初值
procedure SetPen(Value: TPen);
procedure SetBrush(Value: TBrush);
function GetBrush: TBrush;
function GetPen: TPen;
protected
{ Protected declarations }
procedure Paint;override;
public
{ Public declarations }
//构造函数
constructor Create(AOwner: TComponent); override;
//析构函数
destructor Destroy; Override;
published
{ Published declarations }
property DragCursor;
property DragMode;
property OnDragDrop;
property OnDragOver;
property OnEndDrag;
property onMouseDown;
property onMouseMove;
property onMouseup;
//设置缺省的组件大小 65个象素点
property Height default 65;
property Width default 65;
//公布shape pen brush 使用户在使用组件时能改变Canvas的性质
property Shape:TSampleShapeType read FShape write SetShape;
property Pen: TPen read GetPen write SetPen;
property Brush: TBrush read GetBrush write SetBrush;
end;
//注册图形组件
procedure Register;
implementation
procedure Register;
begin
RegisterComponents(‘MD4‘, [TSampleShape]);
end;
//设置Shape属性用枚举类型TSampleShapeType
procedure TSampleShape.SetShape(value:TSampleShapeType);
begin
if FShape<>value then
begin FShape:=value;
Invalidate;
end;
end;
//可以在构造函数中设置初值
constructor TSampleShape.Create(AOwner: TComponent);
begin
Inherited Create(AOwner);
width := 65;
Height := 65;
end;
destructor TSampleShape.Destroy;
begin
Inherited destroy;
end;
procedure TSampleShape.Paint;
begin
with Canvas do
begin
case FShape of
sstRectangle,sstSquare:
Rectangle(0,0,Width,Height);
sstRoundRect,sstRoundSquare:
RoundRect(0,0,Width,Height,Width div 4,Height div 4);
sstCircle,sstEllipse:
Ellipse(0,0,Width,Height);
end ;
end ;
end;
procedure TSampleShape.SetBrush(Value: TBrush);
begin
Canvas.Brush.Assign(Value);
end;
procedure TSampleShape.SetPen(Value: TPen);
begin
Canvas.Pen.Assign(Value);
end;
function TSampleShape.GetBrush: TBrush;
begin
Result := Canvas.Brush;
end;
function TSampleShape.GetPen: TPen;
begin
Result := Canvas.Pen;
end;
end.
--------------------------------------------------
Component--- install Component
在对话框中第一项Unit file Name,是组件单元.pas文件的名字。其他只要按缺省OK,就可以了。
下面可以试验一下这个控件的性能。