DELPHI基础教程
第二十章 开发Delphi对象式数据管理功能(三)
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; |