[摘要]TPresistent 类的定义很简单,但是它的意义却不简单,它在 TObject 的基础上对对象做了进一步的强化,本文介绍Delphi类和组件之TPersistent类。
TPersistent 类继承自 TObject 类,在 Delphi 中的定义如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
{
$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 定义了下面三类方法:
对象复制
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
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 方法来实现。
对象关系
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
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 的属性编辑器中显示的属性名,有系统内部使用,用户无需调用。
对象设计
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
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 的基础上对对象做了进一步的强化,使对象可以相互复制,可以相互关联,可以持久存在(即:可以被保存成文件,以后可以打开该文件继续设计)。