TPersistent类
TPersistent类是由TObject直接派生的。凡是由TPersistent派生的对象都能够进行流操作。
因为所有的组件都是由TPersistent派生的,所以它们都具有流属性。TPersistent没有定义特殊的属性或事件,虽然它定义了一些对于组件用户和编写者有用的方法。下面列举了TPersistent类定义的一些方法。
TPersistent类的方法:
Assign() 这个公用方法允许一个组件把与另一个组件相关的数据赋给自己。
AssignTo() 这是个私有方法,TPersistent派生类必须实现它的定义。当这个方法被调用时,TPersistent将自己抛出一个异常。一个组件可以通过AssignTo()把自己的数据值赋给另一个实例或类,与Assign()相反。
DefineProperties() 这个私有方法允许组件编写者定义组件如何存储特别的或非公用的属性。该方法一般为组件存储诸如二进制数据等非简单数据类型的数据。
TPersistent 类继承自 TObject 类,在 Delphi 中的定义如下:
{ $M+ }
TPersistent = class(TObject)
private
{ 管理:复制组件 }
procedure AssignError(Source: TPersistent);
protected
{ 管理:复制组件 }
procedure AssignTo(Dest: TPersistent); virtual;
{ 持久化(流):定义属性 }
procedure DefineProperties(Filer: TFiler); virtual;
{ 管理:组件关联 }
function GetOwner: TPersistent; dynamic;
public
{ 析构函数 }
destructor Destroy; override;
{ 管理:复制组件 }
procedure Assign(Source: TPersistent); virtual;
{ 设计:内部使用 }
function GetNamePath: string; dynamic;
end;
{ $M- }
VCL 的基类 TObject 本身不支持 RTTI(运行时类型信息),TPersistent 类通过 { $M+ } 编译指令提供了 RTTI 的功能,打开了 M 开关后,Delphi 在编译该对象时,会把对象的类型信息也编译进可执行文件,这样在运行时就可以动态的获得对象的属性,方法等信息,所有的 VCL 可视化组件都是从 TPersistent 派生出来的,因此可以将组件信息保存成 DFM 文件,可以在运行时加载。
我们来看看 TPersistent 做了什么,TPersistent 定义了下面三类方法:
【对象复制】
private
procedure AssignError(Source: TPersistent);
protected
procedure AssignTo(Dest: TPersistent); virtual;
public
procedure Assign(Source: TPersistent); virtual;
end;
----------
{ 将 Source 的数据复制给 Self }
procedure TPersistent.Assign(Source: TPersistent);
begin
if Source <> nil then Source.AssignTo(Self) else AssignError(nil);
end;
{ 将 Self 的数据复制给 Source }
procedure TPersistent.AssignTo(Dest: TPersistent);
begin
Dest.AssignError(Self);
end;
{ 复制异常处理 }
procedure TPersistent.AssignError(Source: TPersistent);
var
SourceName: string;
begin
if Source <> nil then
SourceName := Source.ClassName
else
SourceName := 'nil';
raise EConvertError.CreateResFmt(@SAssignError, [SourceName, ClassName]);
end;
----------
这里的对象复制方法没有任何实际代码,期待子类覆盖,以实现各自的复制功能。
在 VCL 中很多的类都实现了各自的 Assign 方法,比如最常见的 TStrings 类就覆盖了 Assign 方法提供了字符串列表的复制功能。要将一个列表框中的所有内容移动到另一个列表中,也可以使用 Assign 方法来实现。
【对象关系】
protected
function GetOwner: TPersistent; dynamic;
public
function GetNamePath: string; dynamic;
end;
----------
{ 获取当前组件的拥有者 }
function TPersistent.GetOwner: TPersistent;
begin
Result := nil;
end;
{ 用于获取在 IDE 的属性编辑器中显示的属性名 }
function TPersistent.GetNamePath: string;
var
S: string;
begin
Result := ClassName;
if (GetOwner <> nil) then
begin
S := GetOwner.GetNamePath;
if S <> '' then
Result := S + '.' + Result;
end;
end;
----------
这里的 GetOwner 没有任何实际代码,期待子类覆盖,以实现各自的 GetOwner 功能。TComponent 类就覆盖了 GetOwner 方法,可以很方便的管理父子组件。
GetNamePath 用于获取在 IDE 的属性编辑器中显示的属性名,有系统内部使用,用户无需调用。
【对象设计】
protected
procedure DefineProperties(Filer: TFiler); virtual;
public
destructor Destroy; override;
end;
----------
{ 用于元件设计者自定义非 published 属性的存储和读取方法 }
procedure TPersistent.DefineProperties(Filer: TFiler);
begin
end;
{ 销毁对象 }
destructor TPersistent.Destroy;
begin
RemoveFixups(Self);
inherited Destroy;
end;
----------
DefineProperties 的作用是为了实现对象的持久化。一个对象要持久存在,就必须将它流化(Streaming),保存到一个磁盘文件(.dfm 文件)中。TPersistent 并没有实现该方法,期待子类去实现。
TComponent 类就覆盖了 DefineProperties 方法,并且定义了 Top 和 Left 属性,这样,所有继承自 TComponent 的组件都默认具有 Top 和 Left 属性。DefineProperties 方法是通过 TReader、TWriter 两个核心类来实现对象的持久化,具体可以参考“Delphi 的持续机制”等相关资料。
Destroy 用来销毁一个对象,其中调用了 RemoveFixup 方法,关于这个方法,Delphi 没有相关说明。不过我在 ktop 论坛看到的关于 RemoveFixup(Self); 的部分解释:
----------
FixupList 主要是使用在 ReadComponent 的时候。通常在建造一个 TForm 时,TForm.Create 会从 exe 档的 rsrc 资源里,读入关于 Form 中所有的 Published 的属性和组件,并为 Form 中的所有 Published 属性和组件变量进行设定。而一般的属性可直接读入设定值,嵌入的组件则利用 RTTI 来建造,但另有一状况则是属性是参考到别的对象,像 TDBGrid 参考到 TDataSource,因为被参考的对象可能是别的 Form 或 Module 所建造的,而在本身的 Form 建立时,如果找不到让参考目标,这时就要将这个修正需求加入 FixupList,以待将来该对象被加载时可以修正到这个 Form 中的对象参考 。
另外为什么要在组件移除时要做 RemoveFixups 是因组件要被移除,那它必需将参考到这个对象的对象指针清除,不然那些对象参考会指到非法的地址而不自知。
举个例子,我们常利用 TDataModule 来置放 TTable,TDataSouse,等等……,而这些 TDataMoudle 上面的组件,常被我们的 Form 里面的组件所参考,这时候就需要 FixupList 来做修正的工作了。
以上是我的想法,也许有误,欢迎指正,请不吝赐教。
----------
TPresistent 类的定义很简单,但是它的意义却不简单,它在 TObject 的基础上对对象做了进一步的强化,使对象可以相互复制,可以相互关联,可以持久存在(即:可以被保存成文件,以后可以打开该文件继续设计)。