20.2.1.1 TFiler
对象的属性和方法1. Root属性
声明:property Root: TComponent;
Root 属性给Filer 对象指出被读写的对象中哪一个对象是根或主要拥有者。RootComponent 和WriteRootComponent 方法在读和写部件及其拥有的部件前先设置Root 的值。
2. Ancestor属性
声明:property Ancestor: TPersistent;
Ancestor属性用于往继承下来的窗体中写部件,因为当写部件时,Write 对象只需要写入与所继承的部件不同的属性,所以在写之前要跟踪每个继承的部件,并且比较它们的属性。
如果Ancestor为nil ,就表示没有相应的继承的部件,Writer 对象应当将部件完全写入流。Ancestor一般为nil ,只有当调用WriteDescendant 和WriteDescendantRes时,才给赋值。当编写和覆盖DefineProperties时,必须设置Ancestor 的值。
3. IgnoreChildren属性
声明:property Ignorechildren: Boolean;
IgnoreChildren属性使一个Writer 对象存储部件时可以不存储该部件拥有的部件。如果IgnoreChildren属性为True ,则Writer 对象存储部件不存它拥有的子部件。否则,Writer 对象将所有其拥有的对象写入流。
4. Create 方法
声明:constructor Create(Stream: TStream; BufSize: Cardinal);
Create 方法创建一个新的Filer 对象,建立它和流Stream 的联系;并且给它分配一个缓冲区Buffer 。Buffer 的大小由BufSize指定。
5. Defineproperty方法
声明:procedure Defineproperty(const Name: String; ReadData: TReaderProc;
WriteData: TWriterProc; HasData: Boolean); virtual; abstract;
Defineproperty 方法定义Filer 对象将作为属性存储的数据。Name 参数描述接受的属性名,该属性不在published 部分定义。ReadData 和WriteData 参数指定在存取对象时读和写所需数据的方法。HasData 参数在运行时决定了属性是否有数据要存储。
只有当对象有数据要存储时,才在该对象的DefineProperties中调用DefineProperty 。DefineProperties有一个Filer 对象作为它的参数,调用的就是该Filer 对象的DefineProperty 和DefineBinaryProperty 方法。当定义属性时,Writer 对象应当引用Ancestor属性,如果该属性非空,Writer 对象应当只写入与从Ancestor 继承的不同的属性的值。
一个最简单的例子是TComponent 的DefineProperties 方法。尽管TComponent 没有在published中定义Left 、Top属性,但该方法存储了部件的位置信息。
procedure TComponent.DefineProperties(Filer: TFiler);
begin
Filer.DefineProperty('Left', ReadLeft, WriteLeft, LongRec(FDesignInfo).Lo <> 0);
Filer.DefineProperty('Top', ReadTop, WriteTop, LongRec(FDesignInfo).Hi <> 0);
end;
6. DefineBinaryproperty 方法
声明:procedure DefineBinaryproperty(const Name: String;
ReadData, WriteData: TStreamProc;
HisData: Boolean); virtual; abstract;
DefineBinaryProperty 方法定义Filer 对象作为属性存储的二进制数据。Name 参数描述属性名。ReadData 和WriteData 参数描述所存储的对象中读写所需数据的方法。HasData 参数在运行时决定属性是否有数据要存。
DefineBinaryProperty和DefineProperty方法的不同之处在于,二进制型的属性直接用Stream 对象读写,而不是通过Filer 对象。通过ReadData 和WriteData 传入的方法,直接将对象数据写入流或从流读出。
DefineBinaryProperty属性用得较少。只有标准的VCL 对象定义了象图形、图像之类的二进制属性的部件中才用它。
7. FlushBuffer 方法
声明:procedure FlushBuffer; virtual: abstract;
FlushBuffer 方法用于使Filer 对象的缓冲区与相联的Stream 对象同步。对Reader 对象来说,是通过重新分配缓冲区;对于Writer 对象是通过写入当前缓冲区。
FlushBuffer是一个抽象方法,TReader 和TWriter 都覆盖了它,提供了具体实现。
20.2.1.2 TFiler 对象的实现原理
TFiler 对象是Filer 对象的基础类,它定义的大多数方法都是抽象类型的,没有具体实现它,这些方法要在TReader 和TWrite中覆盖。但它们提供了Filer 对象的框架,了解它无疑是很重要的。
1. TFiler 对象属性的实现
TFiler 对象定义了三个属性:Root 、Ancestor 和IgnoreChildren 。正如定义对象属性通常所采用的方法那样,要在private 部分定义存储属性值的数据域,然后在public 或Published 部分定义该属性,并按需要增加读写控制。它们的定义如下:
TFiler = class(TObject)
private
…
FRoot: TComponent;
FAncestor: TPersistent;
FIgnoreChildren: Boolean;
public
…
property Root: TComponent read FRoot write FRoot;
property Ancestor: TPersistent read FAncestor write FAncestor;
property IgnoreChildren: Boolean read FIgnoreChildren write FIgnoreChildren;
end;
它们在读写控制上都是直接读写私有的数据域。
在介绍TReader 和TWriter 的实现,我们还会看到这几个属性的原理介绍。
2. TFiler 对象方法的实现
在TFiler 对象定义的众多方法中很多都是抽象类方法,没有具体实现。在TFiler 的后继对象TReader中覆盖了这些方法。在后面章节,会介绍这些方法的实现。
在TFiler 对象中有具体实现的有两个方法Create 和Destroy 。
⑴ Create 方法的实现
Create 方法是TFiler 的构造方法,它有两个参数Stream 和BufSize 。Stream是指定与TFiler对象相联系的Stream对象,Filer对象都是用Stream对象完成具体的读写。BufSize是TFiler对象内部开设的缓冲区的大小。Filer 对象内部开设缓冲区是为了加快数据的读写,它的实现如下:
constructor TFiler.Create(Stream: TStream; BufSize: Integer);
begin
FStream := Stream;
GetMem(FBuffer, BufSize);
FBufSize := BufSize;
end;
FStream 、FBuffer 和FBufSize 都是TFiler在private部分定义的数据域。FStream 表示与Filer 对象相联的Stream 对象,FBuffer指向Filer对象内部开设的缓冲区,FBufSize是内部缓冲区的大小。Create 方法用Stream 参数值给FStream 赋值,然后用GetMem 分配BufSize 大小的动态内存作为内部缓冲区。
⑵ Destroy 方法的实现
Destroy 方法是TFiler 对象的析构函数,它的作用就是释放动态内存。
destructor TFiler.Destroy;
begin
if FBuffer <> nil then FreeMem(FBuffer, FBufSize);
end;
20.2.2 TWriter 对象
TWriter 对象是可实例化的,往流中写数据的Filer 对象。TWriter 对象直接从TFiler 继承而来,除了覆盖从TFiler 继承的方法外,还增加了大量的关于写各种数据类型(如Integer 、String和Component 等) 的方法。TWriter 对象和TReader 对象配合使用将使对象读写发挥巨大作用。
20.2.2.1 TWriter 对象的属性和方法
1. Position属性
声明:property Position: Longint;
TWriter 对象的Position属性表示相关联的流中的当前要写的位置,TReader 对象也有这个属性,但与TReader 对象不同的是TWriter 对象的Position 的值比流的Position值小,这一点一看属性实现就清楚了。
2. RootAncesstor属性
声明:property RootAncestor: TComponent;
RootAncestor属性表示的是Root属性所指的部件的祖先。如果Root 是继承的窗体,Writer 对象将窗体拥有部件与祖先窗体中的相应部件依次比较,然后只写入那些与祖先中的不同的部件。
3. Write 方法
声明:procedure Write(const Buf; Count: Longint);
Write 方法从Buf中往与Writer相关联的流中写入Count 个字节。
4. WriteListBegin方法
声明:procedure WriteListBegin;
WriteListBegin 方法往Write 对象的流中写入项目列表开始标志,该标志意味着后面存储有一连串的项目。Reader 对象,在读这一连串项目时先调用ReadListBegin 方法读取该标志位,然后用EndOfList判断是否列表结束,并用循环语句读取项目。在调用WriteListBegin 方法的后面必须调用WriteListEnd 方法写列表结束标志,相应的在Reader 对象中有ReadListEnd 方法读取该结束标志。
5. WriteListEnd 方法
声明:procedure WriteListEnd;
WriteListEnd 方法在流中,写入项目列表结束标志,它是与WriteListBegin相匹配的方法。
6. WriteBoolean 方法
声明:procedure WriteBoolean(Value: Boolean);
WriteBoolean 方法将Value 传入的布尔值写入流中。
7. WriteChar 方法
声明:procedure WriteChar(Value: char);
WriteChar 方法将Value中的字符写入流中。
8. WriteFloat 方法
声明:procedure WriteFloat(Value: Extended);
WriteFloat 方法将Value 传入的浮点数写入流中。
9. WriteInteger 方法
声明:procedure WriteInteger(Value: Longint);
WriteInteger 方法将Value中的整数写入流中。
10. WriteString 方法
声明:procedure WriteString(const Value: string);
WriteString 方法将Value中的字符串写入流中。
11. WriteIdent 方法
声明:procedure WriteIdent(const Ident: string);
WriteIdent 方法将Ident 传入的标识符写入流中。
12. WriteSignature方法
声明:procedure WriteSignature;
WriteSignature 方法将Delphi Filer 对象标签写入流中。WriteRootComponent 方法在将部件写入流之前先调用WriteSignature 方法写入Filer 标签。Reader 对象在读部件之前调用ReadSignature 方法读取该标签以指导读操作。
13. WritComponent方法
声明:procedure WriteComponent(Component: TComponent);
WriteComponent 方法调用参数Component 的WriteState 方法将部件写入流中。在调用WriteState之前,WriteComponent 还将Component 的ComponetnState属性置为csWriting 。当WriteState 返回时再清除csWriting.
14. WriteRootComponent 方法
声明:procedure WriteRootComponent(Root: TComponent);
WriteRootComponent 方法将Writer 对象Root属性设为参数Root 带的值,然后调用WriteSignature 方法往流中写入Filer 对象标签,最后调用WriteComponent 方法在流中存储Root 部件。
20.2.2.2 TWriter 对象的实现
TWriter 对象提供了许多往流中写各种类型数据的方法,这对于程序员来说是很重要的功能。TWrite 对象往流中写数据是依据不同的数据采取不同的格式的。 因此要掌握TWriter 对象的实现和应用方法,必须了解Writer 对象存储数据的格式。
首先要说明的是,每个Filer 对象的流中都包含有Filer 对象标签。该标签占四个字节其值为“TPF0 ”。Filer 对象为WriteSignature 和ReadSignature 方法存取该标签。该标签主要用于Reader 对象读数据( 部件等)时,指导读操作。
其次,Writer 对象在存储数据前都要留一个字节的标志位,以指出后面存放的是什么类型的数据。该字节为TValueType类型的值。TValueType是枚举类型,占一个字节空间,其定义如下:
TValueType = (VaNull, VaList, VaInt8, VaInt16, VaInt32, VaEntended, VaString, VaIdent,
VaFalse, VaTrue, VaBinary, VaSet, VaLString, VaNil, VaCollection);
因此,对Writer 对象的每一个写数据方法,在实现上,都要先写标志位再写相应的数据; 而Reader 对象的每一个读数据方法都要先读标志位进行判断,如果符合就读数据,否则产生一个读数据无效的异常事件。VaList 标志有着特殊的用途,它是用来标识后面将有一连串类型相同的项目,而标识连续项目结束的标志是VaNull 。因此,在Writer 对象写连续若干个相同项目时,先用WriteListBegin写入VaList 标志,写完数据项目后,再写出VaNull 标志;而读这些数据时,以ReadListBegin 开始,ReadListEnd 结束,中间用EndofList 函数判断是否有VaNull 标志。
下面就介绍它们的实现。
1. TWriter 对象属性的实现
TWriter 对象直接从TFiler 对象继承,它只增加了Position 和RootAncestor属性。
RootAncestor属性在private 部分有数据域FRootAncestor 存入其值。在属性定义的读与控制上都是直接读取该值。
Position属性的定义中包含了两个读写控制方法:GetPosition 和SetPosition :
TWriter = class(TFiler)
private
FRootAncestor: TComponent;
…
function GetPosition: Longint;
procedure SetPosition(Value: Longint);
public
…
property Position: Longint read GetPosition write SetPosition;
property RootAncestor: TComponent read FRootAncestor write FRootAncestor;
end;
GetPosition 和SetPosition 方法实现如下:
function TWriter.GetPosition: Longint;
begin
Result := FStream.Position + FBufPos;
end;
procedure TWriter.SetPosition(Value: Longint);
var
StreamPosition: Longint;
begin
StreamPosition := FStream.Position;
{ 只清除越界的缓冲区 }
if (Value < StreamPosition) or (Value > StreamPosition + FBufPos) then
begin
WriteBuffer;
FStream.Position := Value;
end
else FBufPos := Value - StreamPosition;
end;
WriteBuffer是TWriter 对象定义的私有方法,它的作用是将Writer 对象内部缓冲区中的有效数据写入流中,并将FBufPos置为0 。Writer 对象的FlushBuffer 对象就是用WriteBuffer 方法刷新缓冲区。
在SetPosition 方法中,如果Value值超出了边界(FStream.Position ,FStream.Position + FBufPos) ,就将缓冲区中的内容写入流,重新设置缓冲区在流中的相对位置;否则,就只是移动FBufPos指针。
2. TWriter 方法的实现
⑴ WriteListBegin和WriteListEnd的实现
这两个方法都是用于写连续若干个相同类型的值。WriteListBegin写入VaList 标志,WriteListEnd写入VaNull 标志。
procedure TWriter.WriteListBegin;
begin
WriteValue(vaList);
end;
procedure TWriter.WriteListEnd;
begin
WriteValue(vaNull);
end;
这两个方法都调用TWriter 对象的WriteValue 方法,该方法主要用于写入TValueType类型的值。
procedure TWriter.WriteValue(Value: TValueType);
begin
Write(Value, SizeOf(Value));
end;
⑵ 简单数据类型的写入
简单数据类型指的是整型、字符型、字符串型、浮点型、布尔型等。TWriter 对象都定义了相应的写入方法。
WriteInteger 方法用于写入整型数据。
procedure TWriter.WriteInteger(Value: Longint);
begin
if (Value >= -128) and (Value <= 127) then
begin
WriteValue(vaInt8);
Write(Value, SizeOf(Shortint));
end else
if (Value >= -32768) and (Value <= 32767) then
begin
WriteValue(vaInt16);
Write(Value, SizeOf(Smallint));
end else
begin
WriteValue(vaInt32);
Write(Value, SizeOf(Longint));
end;
end;
WriteInteger 方法将整型数据分为8位、16位和32位三种,并分别用vaInt8 、vaInt16和VaInt32 。
WriteBoolean用于写入布尔型数据:
procedure TWriter.WriteBoolean(Value: Boolean);
begin
if Value then
WriteValue(vaTrue) else
WriteValue(vaFalse);
end;
与其它数据类型不同的是布尔型数据只使用了标志位是存储布尔值,在标志位后没有数据。
WriteFloat 方法用于写入浮点型数据。
procedure TWriter.WriteFloat(Value: Extended);
begin
WriteValue(vaExtended);
Write(Value, SizeOf(Extended));
end;
字符串“True ”、“False ”和“nil ”作为标识符传入是由于Delphi 的特殊需要。如果是“True ”、“False ”和“nil ”则写入VaTrue 、VaFalse 和VaNil,否则写入VaIdent标志,接着以字符串形式写入标识符。
WriteString 方法用于写入字符串
procedure TWriter.WriteString(const Value: string);
var
L: Integer;
begin
L := Length(Value);
if L <= 255 then
begin
WriteValue(vaString);
Write(L, SizeOf(Byte));
end else
begin
WriteValue(vaLString);
Write(L, SizeOf(Integer));
end;
Write(Pointer(Value)^, L);
end;
Delphi 的字符串类型有两种。一种长度小于256 个字节,另一种长度小于65536 个字节。WriteString 方法区分这两类情况存储字符串,一种设置VaStirng 标志,另一种设置VaLString 。然后存储字符串的长度值,最后存储字符串数据。
WriteChar 方法用于写入字符。
procedure TWriter.WriteChar(Value: Char);
begin
WriteString(Value);
end;
字符类型的读写是用读写字符串的方法,在读的时候,判断字节数为1时,则为字符型。
⑶ 部件的写入
TWriter 对象中与写入部件有关的方法有WriteSignature 、WritePrefix 、WriteComponent 、WriteDescendant 和WriteRootComponent 。
WriteSignature 方法用于往流中写入Filer 对象标签。
procedure TWriter.WriteSignature;
begin
Write(FilerSignature, SizeOf(FilerSignature));
end;
FilerStgnature是字符串常量,其值为“TPF0 ”,代表对象标签。
WritePrefix 方法用于在写入部件前写入ffInherited 和ffChildPos 标志,这些标志表示部件的继承特征和创建序值特征。
procedure TWriter.WritePrefix(Flags: TFilerFlags; AChildPos: Integer);
var
Prefix: Byte;
begin
if Flags <> [] then
begin
Prefix := $F0 or Byte(Flags);
Write(Prefix, SizeOf(Prefix));
if ffChildPos in Flags then WriteInteger(AChildPos);
end;
end;
如果ffChildPos置位,则存入部件在Owner中的创建序值。更详细的信息请参阅TReader 的ReadPrefix 方法。
WriteComponent 方法往流中写入部件。
procedure TWriter.WriteComponent(Component: TComponent);
function FindAncestor(const Name: string): TComponent;
begin
…
end;
begin
Include(Component.FComponentState, csWriting);
if Assigned(FAncestorList) then
Ancestor := FindAncestor(Component.Name);
Component.WriteState(Self);
Exclude(Component.FComponentState, csWriting);
end;
方法中用Component的WritState 方法写入部件的属性。在写入之前将Component.FComponentState置为csWriting写入完后再将csWriting 复位。
WriteDescendant是根据祖先AAncestor 的情况写入部件Root 。
procedure TWriter.WriteDescendent(Root: TComponent; AAncestor: TComponent);
begin
FRootAncestor := AAncestor;
FAncestor := AAncestor;
FRoot := Root;
WriteSignature;
WriteComponent(Root);
end;
方法先调用WriteSignature 方法写入Filer 对象标签。然后调用WriteComponent 将部件Root写入流。
WriteRootComponent方法则是调用WriteDescendant 方法写入部件,只是将后者的Ancestor 参数以nil值传入。
procedure TWriter.WriteRootComponent(Root: TComponent);
begin
WriteDescendent(Root, nil);
end;