Delphi组件属性
作者:邹飞
E-mail:zouf.tech@ATechSoft.com
Homepage:http://www.atechsoft.com/people/zouf/
注:本文讨论对于Delphi中自定义控件(User-Defined Controls)中的属性问题,而且主要是讨论设计时(In-Design)的属性问题,即控件在Delphi的Object Inspector中看到的属性。本文将对如何实现这些属性进行较详细的说明。
简单属性
熟悉Object Pascal的人都知道,要使控件在Object Inspector中显示出属性,只需在类的published中增加相应的property即可。下面给出一个最简单的例子,它将在Standard面板中注册一个简单的控件,这个控件只有一个自定义属性(Caption: string):
…
TA = class(TWinControl)
private
FCaption: string;
published
property Caption: string read FCaption write FCaption;
end;
procedure Register;
…
procedure Register;
begin
RegisterComponents('Standard', [TOutlookPnl]);
end;
对象属性
如果一个类的属性为对象,在Object Inspector中将会显示成如下的样式,
图中红色标出的属性:Action是一个对象属性(类型为TBaseAction),Delphi在设计时会自动从当前窗体中找出类型与之向上兼容的对象,并列出于下拉列表中(如上图的Action1、Action2、Action3…)
这种属性的实现和简单属性的实现没有什么区别,也是在published中的property中定义即可:
property Action: TBaseAction read FAction write FAction;
List型属性
Delphi中的TListView中的Columns属性、DBGrids中的Columns属性等,它们在Object Inspector中显示的是:
点击右边的“…”按钮会打开如下的窗体:
用户可以在设计时在该窗体中点右键“Add”新的项(item)。
这种类型的属性的本质在于:它的取值为一个Collection,用户可以在弹出的窗体中设计这个Collection中的具体内容。
下面以一个例子来介绍如何实现这种类型的属性:
Unit A;
interface
uses Classes, Windows, SysUtils, Controls;
type
TListProps = class; // 这个类是用于表示该List属性的集合类型
TA = class(TWinControl)
private
FListProps: TListProps;
published
property ListProps: TListProps read FListProps write FListProps; // List属性
public
constructor Create(AOwner: TComponent); override; 这里先创建List属性
destructor Destroy; override; // 记得销毁List属性
end;
// List属性中的Item类型,实际上一个List属性就是一个Collection,而其中的Item的类型必须继承自TCollectionItem
TListItem = class(TCollectionItem)
private
FItem: TWinControl;
public
procedure Assign(Source: TPersistent); override; // 因为需要进行赋值操作,必须重载该方法,将数据域进行copy
published
property Item: TWinControl read FItem write FItem; // 这里如上图中的TListColumn的属性
end;
// List集合类
TListProps = class(TCollection)
private
FOwner: TPersistent; // 这个属性是必须的,因为对于List属性,如果在设计时在弹出的窗口中进行了编辑,必须将修改的结果反应到原始控件中,这里通过这个属性表明了List属性的所有者(Owner),这样在List属性被修改时可以通知Owner作出相应调整,与之相应的,一般在这个类里还需要重载:procedure Update(Item: TCollectionItem); override;这个方法
protected
function GetOwner: TPersistent; override;
public
constructor Create(AOwner: TPersistent);
function Owner: TPersistent;
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('Standard', [TA]);
end;
{ TListItem }
procedure TListItem.Assign(Source: TPersistent);
begin
inherited;
if Source is TListItem then
begin
FItem := TListItem(Source).FItem;
end
else inherited Assign(Source);
end;
{ TListProps }
constructor TListProps.Create(AOwner: TPersistent);
begin
inherited Create(TListItem);
FOwner := AOwner;
end;
function TListProps.GetOwner: TPersistent;
begin
Result := FOwner;
end;
function TListProps.Owner: TPersistent;
begin
Result := FOwner;
end;
{ TA }
constructor TA.Create(AOwner: TComponent);
begin
inherited;
FListProps := TListProps.Create(Self);
end;
destructor TA.Destroy;
begin
FreeAndNil(FListProps);
inherited;
end;
end.
其它部分的理解都是很简单的,这里关键的是集合类TListProps的实现,包括其中Owner、Update等的实现策略都是要好好领会的。
当然这里只是一个最基本的框架,实际的控件可能还需要增加一些东西。
自定义属性
还有最后一种类型的“属性”,它与前面的属性有所不同,一般出现在如下的情形:在控件中点右键,弹出菜单中会有一个属性的编辑,如下图的“Customize…”,点击该菜单项,会
出现如下图的属性编辑窗体:
事实上,这个窗体就是控件编写者本身提供了的一个窗体,那么怎样才能将一个窗体和一个控件绑定起来?并且使得对窗体中值的修改影响到控件的属性值?(事实上,这种属性还是很多的,比如TImageList、TTreeView等)
下面还是通过一个实例来介绍:
TEditorForm: TForm; // 这是一个属性窗口类
TControl: TWinControl; // 这是控件类
假设这两个类已经写好了。
EditorForm: TEditorForm;
Control: TControl;
是两个类的实例。
TComponentEditor用于实现将用户在控件右键菜单上的点击动作映射到属性窗口,从而实现点击不同的菜单项打开不同的属性窗口。
它主要有三个方法:
function GetVerb(Index: Integer): string; override;
它表示菜单上第Index个菜单项的Caption值(如上图中的Customize…)
function GetVerbCount: Integer; override;
它表示一共有多少个菜单项
procedure ExecuteVerb(Index: Integer); override;
它表示点击第Index个菜单项时执行的操作
下面给出一个实例:
type
TControlEditor = class(TComponentEditor)
public
function GetVerb(Index: Integer): string; override;
function GetVerbCount: Integer; override;
procedure ExecuteVerb(Index: Integer); override;
end;
function TControlEditor.GetVerb(Index: Integer): string;
Begin
Result := '&Edit Items...';
End;
function TControlEditor.GetVerbCount: Integer;
Begin
Result := 1;
End;
procedure TControlEditor.ExecuteVerb(Index: Integer);
var
Dlg: TEditorForm;
Begin
Dlg:= TEditorForm.Create(Application);
Try
Dlg.ShowModal;
if Dlg.ModalResult = mrOK then
begin
if Dlg.Modified then
begin
CopyProperty(Dlg, Component); // 将修改过的属性赋值到控件中
Designer.Modified;
end;
end;
finally
Dlg.Free;
End;
End;
最后,要使得两控件和属性之间的映射关系被系统在设计时感知,必须在Register方法中增加RegisterComponentEditor方法的调用,如下例:
procedure Register;
begin
RegisterComponentEditor(TControl, TControlEditor);
end;
###############################################################################