Delphi笔记-自定义组件

Delphi笔记-自定义组件

 (2010-09-08 16:46:34)
标签: 

it

分类: delphi

凡是从TComponent继承下来的对象能够被窗体设计器所操纵。

  创建一个组件时应该注意的几个方面:
1、首先,要确定是否需要创建一个独特的新组件。
2、来好好规划一下组件的工作方式。
3、做好了准备工作,不要急于实际创建组件,得先问问自己:到底需要这个组件干什么?
4、把组件从逻辑上分为几个部分。这样,不仅有利于组件的模块化、简单化,而且能使代码精炼、组织良好。设计组件时,要考虑到可能会有其他程序员要基于你的组件派生出一个新的组件。
5、设计完一个组件,必须先在一个程序中测试一下该组件,才能将它加到组件面板上。
6、最后,把组件及它的图标加到面板上,这样,就可以在应用程序中使用它。

  编写组件的一般步骤:
1) 确定一个祖先类。
2) 创建一个组件单元。
3) 在新组件中添加属性、方法和事件。
4) 测试该组件。
5) 在Delphi中注册该组件。
6) 为该组件建立帮助文件。

当设计组件时,需要考虑下列规则:
1. 保持相互独立
  创建一个自定义组件,一个关键的问题是使最终用户使用起来方便。因此,应当尽可能避免方法之间的相互依赖。
例如,不能强迫用户在使用组件时必须调用某个方法,也不能使一个方法的调用与另一个方法的调用顺序有关。另
外,一个方法调用后不能使组件的其他方法和事件无法使用。最后,应当给方法取一个有意义的名字,使用户从名
称里就可以猜测到该方法大致是干什么的。
2. 方法的可见性
  设计组件时,必须确定方法是在private、public还是在protected部分声明的。不仅要考虑到使用该组件的最终
用户,还要考虑到由该组件派生出另一个组件的用户。

  VCL中的组件基类
--------------------------------------------------------------------------------------------
VCL类   描述
--------------------------------------------------------------------------------------------
TObject   直接从TObject继承下来的类不是组件。有些以此为基类的对象在设计期不需要使用,如TIniFile
TComponent   这是非可视组件的起点,它的特点是在设计时能够以流的方式在IDE上存取
TGraphicControl  创建一个不需要窗口句柄、但要在屏幕上显示的组件时,用这个类作为祖先类
TWinControl   所有需要窗口句柄的组件,都应以该类为基类。该类提供了Windows组件的一般属性和事件
TCustomControl   该类是从TWinControl继承下来的。它具有Canvas属性和Paint()方法,能够控制组件的外观。也用于需要句柄的组件
TCustomXXX  VCL中有些类的属性是不公开的,它们用来作为组件的祖先类。可以以它为祖先类创建出自定义组件,每个组件公开自己的属性
TXXX    一个现有的组件,譬如TEdit、TPanel或TScrollBox。与其创建一个新的组件,不如扩展一个现有的组件。大部分自定义组件都是这样的
--------------------------------------------------------------------------------------------

如果属性是对象,该对象必须是从TPersistent或其派生类继承下来的。

属性的默认值是在组件的Create()中设定的,例如:
constructor TDDGWorthless.Create(AOwner: TComponent);
begin
  inherited;
  FIntegerProp:=100;
end;
属性定义中最后的default关键字并不是属性的默认值,它只是表示:当保存含有该组件的窗体时,该属性的值如果等于default指定的值就将
属性值保存到DFM文件中,否则就不保存。建议尽量使用Default关键字来声明属性,因为这样能够加快打开窗口的速度。
NoDefault关键字用来再次声明指定默认值的属性,不管属性值是多少,都将保存属性的值。

默认数组型属性:
  可以声明一个数组属性使之成为一个组件的默认属性。这样,使用对象实例就像使用数组变量一样。例如:
TDDGPlanets=class(TComponent)
  private
    function GetPlanetName(const aIndex:Integer):String;
  public
    property PlanetName[const aIndex:Integer]:String read GetPlanetName;default;
  end;
TddgPlanets组件中声明了一个PlanetName属性,后面用了关键字default。这样做,TddgPlanets组件的默认属性就是PlanetName。
因此,下面两行代码的结果是一样的:
ShowMessage(ddgPlanets.PlanetName[8]);
ShowMessage(ddgPlanets[8]);
一个对象只能有一个默认的数组型属性,并且不能在派生类中覆盖。

事件:
  事件是一种特殊的属性,事件有可能产生于用户交互操作、操作系统、程序代码。事件的机制就是事件与相应的代码相联系,当事件发生时,
就会调用相应的代码。事件都有一个相应的事件调度方法。事件调度方法通常是在组件的protected部分声明,由它来检查事件属性是否指向
了用户为自定义组件提供的代码,如果有就调用它。例如:
  TControl=class(TComponent)
  private
    FOnClick:TNotifyEvent;
  protected
    procedure Click;dynamic;//OnClick事件的高度方法
    property OnClick:TNotifyEvent read FOnClick write FOnClick;
  end;
 
procedure TControl.Click;
begin
  if Assigned(FOnClick) then FOnClick(Self);
end;
事件属性实际上就是一方法指针,其类型为TNotifyEvent,TNotifyEvent的声明如下:
  TNotifyEvent=procedure(Sender:TObject) of Object;
关键字of object,表示该过程是一个方法,这意味着一个隐含参数Self会被传递给调用过程,这个参数代表方法实际所在的对象。
作为一个组件编写者,需要编写事件声明、事件属性及事件调度方法的所有代码。

当声明一个TComponent派生类的构造器时,务必要使用override关键字。如果一个类是从TComponent的上级即TObject或TPersistant
继承下来的,那么它的构造就不能加上override关键字。因为TComponent的构造器是虚拟的,而TComponent的父类的构造器不是虚拟的。

在创建组件时或用户在设计期把组件放到窗体上时,会自动调用组件的构造器;而在设计组件时,应当避免对组件进行任何操作。所以在组件
的构造器中创建或操作其它组件时应该考虑到组件的状态是否不处于设计状态。组件的状态由ComponentState属性来判断,其值如下:
  组件的状态
-------------------------------------------------------------------------------
标志   组件的状态
-------------------------------------------------------------------------------
csAncestor   组件在祖先的窗体引入
csDesigning   处于设计状态,即组件在窗体上,并被窗体设计器操作
csDestroying   组件将被删除
csFixups   组件被链接到一个还没有打开的窗体上,链接完毕,清除标志
csLoading   从文件对象中调入内存
csReading   从一个流中读它的属性值
csUpdating   组件随着祖先窗体的改变而更新
csWriting   把一个属性值写到一个流中
-------------------------------------------------------------------------------
常见的代码如下:
  inherited Create(AOwner);
  if not(csDesigning in ComponentState) then
  begin
    //ToDo;
  end;


注册组件:
  所谓注册组件,就是让Delphi知道把哪个组件放到组件面板上。如果你是使用组件专家(Component Expert)来创建组件的,那就不必考虑
注册的问题,因为Delphi已经自动生成了相关代码。不过,如果组件是手工创建的,就应把Register()过程加到组件单元中。例如:
Unit MyComponent;
interface
...
type
  TMyComp=class(TComponent)
  ...
  end;
  TOtherComp=class(TComponent)
  ...
  end;

procedure Register;

implementation
...
procedure Register;
begin
  RegisterComponent('Custom',[TMyComp,TOtherComp]);//第一个参数表示将组件加到组件面板上的哪一页,第二个参数是组件注册的类型名
end;

如果在设计期提供的不仅仅是RegisterComponents() (如属性编辑器、组件编辑器、专家注册)的调用,可以将Register()过程从组件中分离
出来。设计期包与运行期包应当分离。

如果自定义组件没有图标,那么该组件是不完整的。要创建这样的图标,可以使用Delphi的Image Editor(或者是其他的位图编辑器)来创建
24×2 4的位图,并将位图必须保存到DCR文件。DCR文件与RES文件一样,都是资源文件。所以,如果图标保存在RES文件,那么只要将其
扩展名改为DCR即可。创建了一个位图后,必须给这个位图命名。位图的名称要跟组件的类名相同,而且要大写。DCR文件的名称与组件的单
元名称相同,而且也要大写。该位图文件必须与组件的单元文件位于同一个目录,编译这个单元时,位图资源会自动加到组件库中。


包是若干个单元集中在一起以类似于DLL的形式存储的模块(即BPL文件),包有以下4种类型:
1。运行期包:只在程序运行时才被调用,这种程序需要相应的包文件才能运行。要使用这种包,可以在项目属性对话框中的包标签中设置。
2. 设计期包:在程序进行编译或链接时已被包含到程序中,程序不需要任何包文件就能运行了。
3. 既是运行期包又是设计期包
4. 既不是运行期包又不是设计期包。
包文件的一般格式如下:
package PackageName

requires Package1,Package2,...;  //这个包所需要的其它包,要避免循环引用,引用链不能闭合。

contains                         //这个包所包含的所有单元,这些单元不能同时包含于上面列出的包之中。
  Unit1 in 'Unit1.pas',
  Unit2 in 'Unit2.pas',
  ...;

end.
包在程序开始运行时就被静态地装载,也可以通过调用LoadPackage和UnloadPackage函数来动态地装载包。
注意:当程序中使用包时,每一个包的单元名仍然必须出现在源代码文件的uses子句中。

包的文件扩展名:
.dpk   包的源文件,是包编辑器创建的
.dcp   运行期/设计期包的符号文件,是个编译过的包文件,它包含包的符号信息及IDE需要的头信息
.dcu   编译过的单元,包中包含一个单元,就会有一个.dcu文件
.bpl   运行期/设计期包的库文件,就是包的最终文件,相当于DLL。如果这是个运行期包,就必须把它与应用程序一起分发
  (如果应用程序使用了运行期包的话)。如果是个设计期包,就必须把它分发给需要用它编程的程序员。请注意,必须分
  发单元的源代码或者一个.dcp文件
在Delphi中使用包是只需要在Project Options对话框的Packages页上选中Build with Runtime Packages复选框即可。

编写一个属性编辑器的步骤:
1) 派生出一个属性编辑器对象。
2) 把属性当作文本编辑。
3) 用对话框来编辑属性(可选)。
4) 设置属性编辑器的特性。
5) 注册属性编辑器。

Delphi的DesignIntf.pas单元中声明了几个属性编辑器,它们都是从一个共同的基类TPropertyEditor继承下来的。当创建一个属性编辑器时,
属性编辑器必须继承于TPropertyEditor或它的派生类。

 DesignIntf.pas单元中声明的属性编辑器
----------------------------------------------------------------------------------------------------------------
属性编辑器   描述
----------------------------------------------------------------------------------------------------------------
TOrdinalProperty   这是几个有序属性编辑器的基类,如TIntegerProperty、TEnumProperty、TCharProperty等
TIntegerProperty   适用于编辑整形属性
TCharProperty   适用于编辑字符型属性
TEnumProperty   适用于编辑枚举型属性
TFloatProperty    适用于编辑浮点型属性
TStringProperty   适用于编辑字符串型属性
TSetElementProperty  适用于编辑集合中的元素,每个元素被看作单独的布尔选项
TSetProperty    适用于编辑集合类型的属性
TClassProperty    适用于编辑对象类型的属性
TMethodProperty   适用与编辑方法指针类型的属性
TComponentProperty   适用于编辑引用一个组件的属性。这种编辑器不同于TClassProperty编辑器。相反,这种编辑器允许用户用ActiveControl指定一个组件
TColorProperty    适用于编辑TColor型的属性
TFonTNameProperty   适用于编辑字体的名字,它可以让你从一个下拉列表中选择一种字体
TFontProperty    适用于编辑TFont类型的属性。因为它派生自TClassProperty,它允许进行子属性编辑
------------------------------------------------------------------------------------------------------------------

属性编辑器的用途是以字符串的形式把属性值显示在Object Inspector上。当派生一个属性编辑器对象时,必须覆盖GetValue()和
SetValue()这两个方法。GetValue()是获得属性值以供在Object Inspector中显示,这个值以字符串的形式返回;SetValue()的作
用是根据输入到Object Inspect上的字符串来设置属性的值。

 TPropertyEditor中用于读写属性的方法
-------------------------------------------------------------------------
属性的类型 读取方法  设置方法
-------------------------------------------------------------------------
浮点  GetFloatValue()  SetFloatValue()
事件  GetMethodValue()  SetMethodValue()
有序  GetOrdValue()   SetOrdValue()
字符串  GetStrValue()   SetStrValue()
Variant  GetVarValue()   SetVarValue()、SetVarValueAt()
-------------------------------------------------------------------------

注册属性编辑器:
  注册属性编辑器与注册组件一样,都是在Register过程中进行的,可以调用RegisterPropertyEditor()来注册一个属性编辑器,这个过程是这样声明的:
procedure RegisterPropertyEditor(PropertyType:PTypeInfo;ComponentClass:TClass;const PropertyName:String;EditorClass:TPropertyEditorClass);
  第一个参数叫PropertyType,它是一个指针,指向要编辑的属性的运行期类型信息。属性的运行期类型信息可以通过TypeInfo()获取,
例如:TypeInfo(Integer)。ComponentClass参数用于指定这个属性编辑器所适用的组件类。PropertyName参数用于指定属性的名称。
EditorClass参数用于指定属性编辑器的类型。

用对话框来编辑属性:
unit DlgPro;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, Buttons, DesignIntf;

type
  TDialogProperty=class(TStringProperty)
    function GetAttributes: TPropertyAttributes;override; //复盖该方法,让Object Inspector知道属性的特性
    procedure Edit;override; //复盖该方法,当属性的特性是paDialog时,Object Inspector就在属性值输入框的右边出现一个按钮,当用户点击该按钮时,Object Inspector就自动调用Edit方法,所以应该在Edit方法中创建对话框来设置属性值。
  end;

implementation

function TDialogProerty.GetAttributes:TPropertyAttributes;
begin
  Result:=[paDialog];
end;

procedure TDialogProperty.Edit;
var
  OpenDialog:TOpenDialog;
begin
  OpenDialog:=TOpenDialog.Create(Application);
  try
    OpenDialog.Filter:='Executable Files|*.EXE';
    if OpenDialog.Execute then
      SetStrValue(OpenDialog.FileName);
  finally
     OpenDialog.Free;
  end; 
end;

end.
说明:
  1、它是从TStringProperty继承下来的,因此具有编辑字符串的能力。所以用户在Object Inspector中可以直接输入一个文件名而不必
打开对话框。另外,由于TStringProperty已经覆盖了SetValue()和GetValue()这两个方法,TDialogProperty就没有覆盖它们,但需要覆盖
GetAttributes()方法,以便让Object Inspector知道这个属性可以用一个对话框来编辑。
  2、设定属性编辑器的特性:覆盖TPropertyEditor的GetAttributes()方法来属性编辑器的特性标志。
  TPropertyAttribute标志
-----------------------------------------------------------------------------------------------------------------
特性   属性编辑器的工作方式
-----------------------------------------------------------------------------------------------------------------
paValueList   可以从下拉列表中选择属性值,适用于枚举型的属性诸如TForm.BorderStyle和整型变量如TColor、TCharSet
paSubProperties  包含子属性,子属性右缩进显示。适用于集合类型的属性,如TOpenDialog.Option和TForm.Font
paDialog   Object Inspector上将出现一个省略号按钮,单击这个按钮,将调用属性编辑器的Edit()方法打开一个对话框,用于TForm.Font这样的属性
paMultiSelect   允许为多个组件选择属性值,有的属性没有这个功能,如Name属性
paAutoUpdate   当属性的值有变化,就调用SetValue()把值写到.DFM文件中,适用用于TForm.Caption等属性
paFullWidthName  告诉Object Inspector这值不需要交付,名称应以Object Inspector的全宽交付
paSortList   按字母顺序排列属性值的列表
paReadOnly   属性是只读的
paRevertable   属性的值可以恢复为以前的值。TFont等嵌套的属性不应恢复
-----------------------------------------------------------------------------------------------------------------

当在窗体设计器上选择一个组件时,就会自动创建一个组件编辑器的实例。所有的组件编辑器都是从TComponentEditor继承下来的,组件
编辑器的类型和组件的类型密切相关。在DesignIntf单元中,TComponentEditor是这样声明的:
  TComponentEditor = class(TBaseComponentEditor, IComponentEditor)
  private
    FComponent: TComponent; //组件编辑器对应的组件,必须把它转换为实际的组件类型来访问它的属性。
    FDesigner: IDesigner; //窗体设计器的实例
  public
    constructor Create(AComponent: TComponent; ADesigner: IDesigner); override;
    procedure Edit; virtual;
    procedure ExecuteVerb(Index: Integer); virtual;
    function GetComponent: TComponent;
    function GetDesigner: IDesigner;
    function GetVerb(Index: Integer): string; virtual;
    function GetVerbCount: Integer; virtual;
    function IsInInlined: Boolean;
    procedure Copy; virtual;
    procedure PrepareItem(Index: Integer; const AItem: IMenuItem); virtual;
    property Component: TComponent read FComponent;
    property Designer: IDesigner read GetDesigner;
  end;

说明:
1、当用户在设计期双击组件时就会调用Edit()方法,这个方法一般都会触发一个对话框。Edit()首先调用GetVerbCount(),如果返回的值
是1或更大的数,就调用ExecuteVerb(0)。如果想用这个方法来修改组件,就必须调用Designer.Modified()来修改组件。
2、GetVerbCount()方法的作用是检索组件的快捷菜单上有几个命令。
3、GetVerb()根据Index整型参数返回字符串形式的菜单项文本。
4、当用户从快捷菜单中选择一个命令后, ExecuteVerb()方法就会被调用。这个方法将传递用户所选择命令的序号,序号是从零开始的,
可以根据序号作出不同的处理。
5、如果一个组件没有注册一个专用的组件编辑器,那它就必须使用默认的组件编辑器TDefaultEditor。TDefaultEditor覆盖了Edit()方法,
所以它可以搜索组件的属性并生成(或转向到)OnCreate、OnChanged、OnClick事件中的一个(看谁先被搜索到)。
例子:
//------一个简单的自定义组件----------
type
  TMyComponent=class(TComponent)
  protected
    procedure SayHello;virtual;
    procedure SayGoodbye;virtual;
  end;
 
procedure TMyComponent.SayHello;
begin
  MessageDlg('Hello!',mtInfomation,[mbOK],0);
end;

procedure TMyComponent.SayGoodbye;
begin
  MessageDlg('Goodbye!',mtInfomation,[mbOK],0);
end;

//--------一个简单的组件编辑器--------
type
  TMyComEditor=class(TComponentEditor)
  private
    procedure ExecuteVerb(Index:Integer);override;
    function  GetVerb(Index:Integer):String;override;
    function  GetVerbCount:Integer;override;
  end;

procedure TMyComEditor.ExecuteVerb(Index:Integer);
begin
  case Index of
    0: TMyComponent(Component).SayHello;
    1: TMyComponent(Component).SayGoodbye;
  end;
end;

function TMyComEditor.GetVerb(Index:Integer):String;
begin
  case Index of
    0: Result:='Say Hello';
    1: Result:='Say Goodbye';
  end;
end;

function TMyComEditor.GetVerbCount:Integer;
begin
  Result:=2;
end;

注册组件编辑器:
  注册组件编辑器与注册组件一样,都是在Register过程中进行的,要注册一个组件编辑器,需要调用RegisterCompoentEditor()过程,它是这样声明的:
procedure RegisterCompoentEditor(ComponentClass:TComponentClass;ComponentEditor:TComponentEditorClass);
  第一个参数用于指定组件编辑器所适用的组件类,第二个参数就是要注册的组件编辑器自己。

对非公开的组件数据进行流操作:
  Delphi的IDE能够自动将组件的公开属性流进和流出DFM文件。对非公开的数据要做到流进流出DFM文件,可通过以下方法实现:
第一步是覆盖组件的DefineProperties()方法,这个方法是从TPersistent中继承的,它是这样声明的:
procedure DefineProperties(Filer:TFiler);virtual;
默认情况下,这个方法对流进流出DFM文件的公开属性做读写操作。可以覆盖这个方法,在调用继承的方法之后,可以调用TFiler的
DefineProperty()或DefineBinaryProperty()将你感兴趣的数据变成DFM文件的一部分,这两个方法是这样声明的:
procedure DefineProperty(const Name:String;ReadData:TReaderProc;WriteData:TWriteProc;HasData:Boolean);virtual;
procedure DefineBinaryProperty(const Name:String;ReadData,WriteData:TStreamProc;HasData:Boolean);virtual;
DefineProperty()用于使标准数据类型持久化,诸如字符串、整数、布尔、字符、浮点和枚举。
DefineBinaryProperty()用于把二进制的数据持久化,诸如图像、声音等。
这两个方法的Name参数用于指定应写入DFM文件的属性的名称,并不一定要与访问的内部字段同名。
ReadData参数和WriteData参数在DefineProperty()和DefineBinaryProperty()中是不同的,但作用相同,都是从DFM文件中读写数据。
HasData参数用于指定属性是否需要把数据保存到DFM文件中。
type
  TReaderProc=procedure(Reader:TReader) of Object;
  TWriterProc=procedure(Writer:TWriter) of Object;
  TStreamProc=procedure(Stream:TStream) of Object;
TReader和TWriter都是从TFiler继承下来的。这两种类型的方法实际上是组件的数据与DFM文件的管道。TStreamProc类型的方法是非标准数据和DFM文件之间的管道。
例子:
unit DefProp;
interface
uses
  Windows,Messages,SysUtils,Classes,Graphics,Controls,Forms,Dialogs;
type
  TDefinePropTest=class(TComponent)
  private
    FString:String;
    FInteger:Integer;
    FBinary:Pointer;
    FDataSize:Integer;
    procedure ReadStrData(Reader:TReader);
    procedure WriteStrData(Writer:TWriter);
    procedure ReadIntData(Reader:TReader);
    procedure WriteIntData(Writer:TWriter);
    procedure ReadBinaryData(Stream:TStream);
    procedure WriteBinaryData(Stream:TStream);
    function CanWriteBinaryData:Boolean;
    procedure LoadFromStream(Stream:TStream);
    procedure SaveToStream(Stream:TStream);
    function Equal(Proc:TDefinePropTest):Boolean;
  protected
    procedure DefineProperties(Filer:TFiler);override;
  public
    constructor Create(AOwner:TComponent);override;
  end;
 
implementation

constructor TDefinePropTest.Create(AOwner:TComponent);
begin
  inherited Create(AOwner);
  FString:='The following number is the answer...';
  FInteger:=42;
end;
 
procedure TDefinePropTest.DefineProperties(Filer:TFiler);
begin
  inherited DefineProperties(Filer);
  Filer.DefineProperty('StringProp',ReadStrData,WriteStrData,FString<>'');
  Filer.DefineProperty('IntegerProp',ReadIntData,WriteIntData,true);
  Filer.DefineBinaryProperty('BinaryData',ReadBinaryData,WriteBinaryData,CanWriteBinaryData);
end;

procedure TDefinePropTest.ReadStrData(Reader:TReader);
begin
  FString:=Reader.ReadString;
end;

procedure TDefinePropTest.WriteStrData(Writer:TWriter);
begin
  Writer.WriteString(FString);
end;

procedure TDefinePropTest.ReadIntData(Reader:TReader);
begin
  FInteger:=Reader.ReadInteger;
end;

procedure TDefinePropTest.WriteIntData(Writer:TWriter);
begin
  Writer.WriteInteger(FInteger);
end;

procedure TDefinePropTest.ReadBinaryData(Stream:TStream);
begin
  LoadFromStream(Stream);
end;

procedure TDefinePropTest.WriteBinaryData(Stream:TStream);
begin
  SaveToStream(Stream);
end;

function TDefinePropTest.CanWriteBinaryData(Filer:TFiler):Boolean;
begin
  if Filer.Ancestor<>nil then  //当该组件所在窗体温表被继承时,Filer.Ancestor就不等于nil,这时就进一步判断是要写的数据是否与祖先的数据不同
    Result:=not(Filer.Ancestor is TDefinePropTest) or not Equal(TDefinePropTest(Filer.Ancestor))
  else 
    Result:=FDataSize>0; //判断数据是否为空
end;

procedure TDefinePropTest.LoadFromStream(Stream:TStream);
begin
  if FDataSize>0 then
    FreeMem(FBinary,FDataSize);
  FDataSize:=0;
  FBinary:=AllocMem(Stream.Size);
  FDataSize:=Stream.Size;
  Stream.Read(FBinary^,FDataSize);
end;

procedure TDefinePropTest.SaveToStream(Stream:TStream);
begin
  if FDataSize>0 then
    Stream.Write(FBinary^,FDataSize);
end;

function TDefinePropTest.Equal(Proc:TDefinePropTest):Boolean;
begin
  //请看《Delphi5开发人员指南第22章第32页》
end;

end.

注意:对于字符串类型的数据来说,建议使用ReadString()和WriteString()来读写,千万不要使用看似相似的ReadStr()和WriteStr(),因为它们会破坏DFM文件。

属性的类别:
  在Delphi的Object Inspector中,可以以类别的方式来排列组件的属性,可以使用DesignIntf单元中的RegisterPropertyInCategory()和RegisterPropertiesInCategory()
函数的以某种类别注册组件的属性。TPropertyCategory是所有VCL标准属性类别的基类。
  标准属性类别类
--------------------------------------------------------------------------------------------
类名称    描述
--------------------------------------------------------------------------------------------
TActionCategory  和运行期间的活动有关的属性, TControl的Enable和Hint属性在这个类别里
TDatabaseCategory  和数据库的操作有关的属性,TQuery的DatabaseName和SQL属性都在这个类别里
TDragNDropCategory  和拖放操作有关的属性,TControl的DragCursor和DragKind属性在这个类别里
THelpCategory   和在线帮助有关的属性, TWinControl的HelpContext和Hint属性在这个类别里
TLayoutCategory  和设计期间的组件显示有关的属性, TControl的Top和Lelf属性在这个类别里
TLegacyCategory  和一些较老的操作有关的属性,Ctl3D和ParentCtl3D属性在这个类别里
TLinkgeCategory  可以把一个组件关联或链接到另一个组件的属性,TDataSource的Dataset属性在这个类别里
TLocaleCategory  和位置有关的属性,TControl的ParentBiDiMode和BiDiMode属性在这个类别里
TLocalizableCategory  和数据库操作有关的属性,TQuery的SQL和DatabaseName属性在这个类别里
TMiscellaneousCategory  在哪个类别里都不太合适,也不需要放在哪个特别的类别里的属性。TSpeedButton的AllowAllUp和Name属性都在这个类别里
TVisualCategory  和运行期间组件的显示有关的属性,TControl的Align和Visible属性都在这个类别里
TInputCategory   和数据输入有关(但与数据库操作无关)的属性。TEdit的Enabled和ReadOnly属性都在这个类别里
----------------------------------------------------------------------------------------------
自定义属性类别:
  一个属性类别在代码上表现为从TPropertyCategory中继承下来的一个类。用这种方式来建立一个自定义的属性类别。在大多数的情况下,你要做
的只是覆盖Name()和Description()函数,这两个函数都是TPropertyCategory中虚拟的类函数,它们返回与你自定义的属性类别的类别名和相关描述。
例如:
type
  TSoundCategory=class(TPropertyCategory)
  public
    class function Name:String;override;
    class function Description:String;override;
  end;

function TSoundCategory.Name:String;
begin
  Result:='Sound';
end;

function TSoundCategory.Description:String;
begin
  Result:='This is a custom property category named "sound"';
end;

以某种属性类别注册属性:
  与注册组件相同,都是在Register()过程中进行,例如:
RegisterPropertyInCategory(TActionCategory,TMyComponent,'AProperty'); //AProperty是自定义组件TMyComponent的一个属性。

组件列表属性:TCollection和TCollectionItem
  当一个组件的某个属性的类型是记录列表、对象列表或者是组件列表时,如何流化保存这个属性的数据到DFM文件中呢?有两种方法:
一种是将该属性的类型自定义成一个类,再将该类的对象作为该组件的属性,例如TMemo的Lines属性就是一个TStrings对象,TStrings
对象负责流化保存各行数据到DFM文件中;另一种方法是:先自定义两个类,一个继承自TCollection,一个继承自TCollectionItem,
将继承自TCollection的类的对象作为该组件的属性,用继承自TCollectionItem的类的对象来操纵单个记录、对象或组件,用继承自
TCollection的类的对象来操纵继承自TCollectionItem的类的对象,对于继承自TCollectionItem的类,它的所有published属性(这些属性其实就是该对象操纵的记录、对象或组件的需要流化的数据域),
Delphi都会自动流化保存到DFM文件中。这样就可以实现流化保存记录列表、对象列表或者是组件列表到DFM文件中了。例如:
TStatusBar的Panels属性,它的类型是TStatusPanels,TStatusPanels继承自TCollection,是用来操纵TStatusPanel
类的对象的,而TStatusPanel是继承自TCollectionItem的类对象。
例子:
type
  TDDGRunButton=class(TButton)
  private
    FCommandLine:String;
    FLeft:Integer;
    FTop:Integer;
    ...
  published
    property Left:integer read FLeft write SetLeft;
    property Top: Integer read FTop write SetTop;
  end;

  TRunButton=class(TCollectionItem)
  private
    FCommandLine:String; //保存命令行
    FLeft:Integer;
    FTop:Integer;
    FRunButton:TDDGRunButton; //该对象要操纵的记录、对象或组件的引用
    ...
  public
    constructor Create(Collection:TCollection);override;//复盖构造函数,使该对象依附于TCollection对象
  published
    //这些公开的属性将会被流化,这些属性与FRunButtom所引用的记录、对象或组件的需要流化的数据域相同
    property CommandLine:String read FCommandLine write SetCommandLine;
    property Left:Integer read FLeft write SetLeft;
    property Top:Integer read FTop write SetTop;
  end;

  TRunButtons=class(TCollection)
  private
    FMyComp:TMyComponent; //引用TMyComponent组件,该组件的属性的类型就是记录列表、对象列表或组件列表
    function GetItem(Index:Integer):TRunButton;
    procedure SetItem(Index:Integer;value:TRunButton);
  public
    constructor Create(MyComp:TMyComponent);override; //复盖构造函数,使该对象依附于具有类型为记录列表、对象列表或组件列表的属性的组件
    property Items[Index:Integer]:TRunButton read GetItem write SetItem;default;
  end;
 
  TMyComponent=class(TScrollBox)
  private
    FRunButtons:TRunButtons;
    procedure SetRunButtons(value:TRunButtons);
  public
    constructor Create(AOwner:TComponent);override;
  published
    property RunButtons:TRunButtons read FRunButtons write SetRunButtons;
  end;
{TRunButton}
constructor TRunButton.Create(Collection:TCollection);
begin
  inherited Create(Collection);//让TRunButton对象依附于TCollection对象
  FRunButton:=TDDGRunButton.Create(TRunButtons(Collection).FMyComp);
  FRunButton.Parent:=TRunButtons(Collection).FMyComp;
  FCommandLine:=FRunButton.CommandLine;
  FLeft:=FRunButton.Left;
  FTop:=FRunButton.Top;
end;

procedure TRunButton.SetCommandLine(value:String);
begin
  if FRunButton<>nil then
  begin
    FCommandLine:=value;
    FRunButton.CommandLine:=value;
  end;
end;

procedure TRunButton.SetLeft(value:Integer);
begin
  if FRunButton<>nil then
  begin
    FLeft:=value;
    FRunButton.Left:=value;
  end;
end;

procedure TRunButton.SetTop(value:Integer);
begin
  if FRunButton<>nil then
  begin
    FTop:=value;
    FRunButton.Top:=value;
  end;
end;
{TRunButtons}
constructor TRunButtons.Create(MyComp:TMyComponent);
begin
  inherited Create(TRunButton);//让TRunButtons对象能操纵TRunButton对象
  FMyComp:=MyComp;//让TRunButtons对象成为MyComp组件的属性
end;

function TRunButtons.GetItem(Index:Integer):TRunButton;
begin
  Result:=TRunButton(inherited GetItem(Index));
end;

procedure TRunButtons.SetItem(Index:Integer;value:TRunButton);
begin
  inherited SetItem(Index,value);
end;
{TMyComponent}
constructor TMyComponent.Create(AOwner:TComponent);
begin
  Inherited Create(AOwner);
  FRunButtons:=TRunButtons.Create(Self);
end;

procedure TMyComponent.SetRunButtons(value:TRunButtons);
begin
  FRunButtons.Assign(value);
end;

posted @ 2015-07-30 12:16  LAOS  阅读(1027)  评论(0编辑  收藏  举报