DELPHI基础教程
第二十章 开发Delphi对象式数据管理功能(四)
20.2.3 TReader 对象 TReader 对象是可实例化的用于从相联系的流中读取数据的 Filer 对象。 TReader 对象从 TFiler 继承下来,除了从 TFiler 继承的属性和方法外, TReader 声明了不少属性、方法和事件。 Owner 和 Parent 属性用于表示从 Reader 对象的流中读取的部件的拥有者和双亲结点。 OnError , OnFindMethod 和 OnSetName 事件使应用程序在运行中读数据时能定制响应方式。除了覆盖了一些从 TFiler 对象中继承的方法外, TReader 对象还定义大量的读不同类型的数据和触发事件的方法。
20.2.3.1 TReader 对象的属性和方法
1. Owner 属性 声明: property Owner: TComponent; Reader 对象的 Owner 属性存储了将用来给从 Reader 的流中读出的部件的 Owner 属性赋值的部件。 2. Parent 属性 声明: property Parent: TComponent; Parent 属性存储将用来给从 Reader 的流中读出所有控制的 Parent 属性赋值的部件。 3. Position 属性 声明: propertion: Longint; Reader 对象的 Position 属性表示相联的流中读的当前位置。 Position 的值还应包括读缓冲区的大小。对于 Reader 对象, Position 的值大于流的 Position 的值。如果将 Position 的值设得超过当前缓冲区,将引起调用 FlushBuffer 。 4. BeginReferences 方法 声明: procedure BeginReferences; BeginReferences 方法启动一连串关于读部件的命令,这些部件包含相互间的交叉引用。在使用上通常和 FixupReferences 和 EndReferences 一起放在 Try … finally 程序块中。 在调用了 BeginReferences 后, Reader 对象创建读取所有对象和名字的列表。所有的独立对象被读出后,调用 FixupReferences 方法将名字的相互从流中转移到对象实例中。最后调用 EndReferences 方法释放列表。 处理部件相互引用的程序块形式如下:
BeginReferences; { 创建临时列表 } try { 读出所有部件并将它们的名字放在一临时列表中 } … FixupReferences; { 分 解 } finally EndReferences; { 释放临时列表 } end;
5. FixUpReferences 方法 声明: procedure FixupReferences; FixupReferences 方法分解从流中读出的存在各种相互依赖部件的引用关系。 FixupReferences 总在 try … finally 块中并配合 BeginReferences 和 EndReferences 一起使用。 6. EndReferences 方法 声明: procedure EndReferences; EndReferences 方法终止处理相互引用的块操作,释放对象列表。它总配合 BeginReferences 和 FixupReferences 一起使用。 7. ReadListBegin 方法 声明: procedure ReadListBegin; ReadListBegin 方法从 Reader 对象相联的流中读取列表开始标志。如果流中紧接着要读取的项目不是一个由 WritelistBegin 方法写入的列表起始标志, ReadListBegin 将引起一个读异常事件。 通常在调用 ReadlistBegin 方法之后,紧跟着一个读项目的循环,循环以 EndfList 方法返回 True 终止条件。这时,预示流中的下一个项目是列表结束标志,需要调用 ReadListEnd 方法。 8. ReadListEnd 方法 声明: procedure ReadListEnd; ReadListEnd 方法从流中读取列表结束标志。如果所读的项目不是一个列表结束标志, ReadListEnd 方法引发一个 EReadError 异常事件。 9. EndOfList 方法 声明: function EndOfList: Boolean; 如果 Reader 对象读到项目列表结果标志, EndOfList 方法返回 True 。 TStrings 对象在从 Reader 对象读取项目列表时使用了 ReadListBegin 和 ReadListEnd 方法。下面的 ReadData 是 TStrings 的方法,用于在 DefineProperties 方面中读 string 数据。
procedure TStrings.ReadData(Reader: TReader); begin Reader.ReadListBegin; { 读列表开始标志 } Clear; { 清除已有的字符串 } while not Reader.EndOfList do { 只要还有数据 … } Add(Reader.ReadString); { …读一个字符串并将其加在列表中 } Reader.ReadListEnd; { 越过列表结束标志 } end;
10. ReadSignature 方法 声明: procedure ReadSignature; ReadSignature 方法从流中读取部件之前首先调用 ReadSignature 方法。在载入对象之前检测标签。 Reader 对象就能防止疏忽大意,导致读取无效或过时的数据。 Filer 标签是四个字符,对于 Delphi 2.0 ,该标签是“ TPF0 ”。 11. ReadPrefix 方法 声明: procedure ReadPrefix(var Plags: TFilerFlags; var AChild, Pos: Integer); ReadPrefix 方法的功能与 ReadSignature 的很相象,只不过它是读取流中部件前面的标志 (PreFix) 。当一个 Write 对象将部件写入流中时,它在部件前面预写了两个值,第一个值是指明部件是否是从祖先窗体中继承的窗体和它在窗体中的位置是否重要的标志 ; 第二个值指明它在祖先窗体创建次序。 ReadComponent 方法自动调用 ReadPrefix 。但如果需要独立读取部件的预读标志,也可直接调用该方向。 12. OnFindMethod 事件 声明: property OnFindMethod: TFindMethodEvent; OnFindMethod 事件,发生在 Reader 对象读取对象的方法指针时,属性为方法指针的通常都是事件。 响应 OnFindMethod 事件的理由,通常是处理过程找不到方法的情况。在 FindMethod 方法没有找到由 Name 指定的方法的情况下,如果它将 OnFindMethod 方法的 Error 参数设为 True ,将引起 ReadError 异常事件;反之,将 Error 参数置为 False ,将防止 FindMethod 方法引发异常事件。 13. Error 方法 声明: function Error(const Message: String): Boolean; virtual; Error 方法定义在 Reader 对象的 protected 部分,它是用于 Reader 对象的 OnError 事件。其返回值决定是否继续错误处理过程。如果返回值为 True ,则表示用程序应当继续错误处理;如果返回值为 False ,则表示错误情况被忽略。 如果读部件或属性出错。 Reader 对象调用 Error 方法。缺省情况下, Error 将返回值设为 False ,然后调用 OnError 事件处理过程。 TReader 对象总是在 try … except 程序块的 except 部分,并提供用户忽略错误的机会。 Error 的使用方法如下:
try … { 读部件 } except on E: Exception do begin …{ 执行一些清除操作 } if Error(E.Message) then raise; end; end;
14. OnError 事件 声明: property OnError: TReaderError; 当 Reader 对象读取数据出错时将引发 OnError 事件。通过处理 OnError 事件,可以有选择地处理或忽略错误。 传给 OnError 事件处理过程的最后一个参数是名为 Handled 的 var 参数。在缺省情况下, Error 方法将 Handled 置为 True 。这将阻止错误更进一步处理。如果事件处理过程仍旧将 Handled 置为 False , Reader 对象将引发一个 EReadError 异常事件。
15. SetName 方法 声明: procedure SetName(Component: TComponent; var Name: String virtual); SetName 方法允许 Reader 对象在将从流中读取的部件的 Name 值赋给部件的 Name 属性前修改 Name 值。 ReadComponent 方法在读取部件的属性值和其它数据前先读部件的类型和名字在读完名字后, ReadComponent 将所读的名字作为 Name 参数传给 SetName , Name 是个 var 参数,因此 SetName 能在返回前修改字符串值。 SetName 还调用了 OnSetName 事件处理过程,将名字字符串作为 var 参数传入事件处理过程中,因此,事件处理过程也可修改字符串的值。 16. OnSetName 事件 声明: property OnSetName: TSetNameEvent; OnSetName 事件发生在 Read 对象设置部件的 Name 属性前, OnSetName 事件处理过程的 var 参数 Name 参数是一个 var 参数,因此,事件处理过程再将 Name 赋给部件前,可以修改 Name 的值。这对于想过滤窗体中部件的名字是很有帮助的。 下面的 OnSetName 事件处理过程,命名了名字中包含“ Button ”的部件,并用“ PushButton ”替代。
procedure TForm1.ReaderSetName(Reader: TReader; Component: TComponent; var Name: string); var ButtonPos: Integer; begin ButtonPos := Pos('Button', Name); if ButtonPos <> 0 then Name := Copy(Name, 1, ButtonPos - 1) + 'PushButton' + Copy(Name, ButtonPos + 6, Length(Name)); end;
17. ReadValue 方法 声明: function ReadValue: TValueType; ReadValue 方法读取流中紧着的项目的类型,函数返回后,流的指针移到值类型指示符之后。 TValueType 是枚举类型。存储在 Filer 对象的流中的每个项目之前都有一个字节标识该项目的类型,在读每个项目之前都要读取该字节,以指导调用哪个方法来闱取项目。该字节的值就 TValuetype 定义的值类型之一。 18. NextValue 方法 声明: function Nextvalue: TValuetype; Nextvalue 方法的作用也是返回 Reader 对象流中紧接着的项目的类型,它与 ReadValue 的区别在于并不移动指针位置。 19. ReadBoolean 方法 声明: function ReadBoolean: Boolean; ReadBoolean 方法从 Reader 对象的流中读取一个布尔值,并相应地移动流位置指针。 20 、 ReadChar 方法 声明: function ReadChar: char; ReadChar 方法从 Reader 对象的流中读取一个字符。 21. ReadFloat 方法 声明: function ReadFloat: Extended; ReadFloat 方法从流中读取浮点数。 20. ReadIdent 方法 声明: function ReadIdent: string; ReadIdent 方法从流中读取标识符。 23. ReadInteger 方法 声明: function ReadInteger: Longin ReadInteger 方法从流中读取整型数字。 24.ReadString 方法 声明: function Read String: string; ReadString 方法从 Reader 对象的流中读取一个字符串,并返回字符串中的内容。该字符串是由 Writer 对象的 WriteString 方法写入。
20.2.3.2 TReader 对象的实现
Filer 对象的作用主要是 Delphi 用来在 DFM 文件中读写各种类型的数据(包括部件对象)。这些数据的一个本质特征是变长,而且 Filer 对象将读写数据操作抽象化,包装成对象提供了大量的读写方法,方便了程序的调用。因此在应用程序中可以广泛使 Filer 对象,充分利用 Delphi 的面向对象技术。而且 Filer 对象与 Stream 对象捆绑在一起,一方面可以在各种存储媒介中存取任意格式的数据;另一方面,由于充分利用面向对象的动态联编,各种读写方法的使用方法是一致的,因此,方法调用很简单。下面我们着重介绍 Reader 对象中与读写数据操作有关的属性和方法的实现。 1. TReader 属性的实现 在 TReader 对象的属性实现中我们重点介绍 Position 的实现。 Position 属性的定义了使用了读写控制,它们分别是 GetPosition 和 SetPosition 方法。
TReader = class(TFiler) private … function GetPosition: Longint; procedure SetPosition(Value: Longint); public … property Position: Longint read GetPosition write SetPosition; end;
Postition 的读写控制方法如下:
function TReader.GetPosition: Longint; begin Result := FStream.Position + FBufPos; end;
procedure TReader.SetPosition(Value: Longint); begin FStream.Position := Value; FBufPos := 0; FBufEnd := 0; end;
在 TReader 的父对象 TFiler 对象中介绍过 FBufPos 和 FBufEnd 变量。 Filer 对象内部分配了一个 BufSize 大小的缓冲区 FBufPos 就是指在缓冲区中的相对位置, FBufEnd 是指在缓冲区中数据结束处的位置 ( 缓冲区中的数据不一定会充满整个缓冲区 ) 。 在 GetPosition 方法中可以看到 Reader 对象的 Position 值和 Stream 对象的 Position 值是不同的。 Reader 对象多了一个 FButPos 的编移量。 2. Defineproperty 和 DefineBinaryproperty 方法的实现 这两个方法是虚方法,在 TFiler 中是抽象方法,在 TReader 和 TWriter 对象中才有具体的实现。 它们在 TReader 中的实现如下:
procedure TReader.DefineProperty(const Name: string; ReadData: TReaderProc; WriteData: TWriterProc; HasData: Boolean); begin if CompareText(Name, FPropName) = 0 then begin ReadData(Self); FPropName := ''; end; end;
procedure TReader.DefineBinaryProperty(const Name: string; ReadData, WriteData: TStreamProc; HasData: Boolean); var Stream: TMemoryStream; Count: Longint; begin if CompareText(Name, FPropName) = 0 then begin if ReadValue <> vaBinary then begin Dec(FBufPos); SkipValue; FCanHandleExcepts := True; PropValueError; end; Stream := TMemoryStream.Create; try Read(Count, SizeOf(Count)); Stream.SetSize(Count); Read(Stream.Memory^, Count); FCanHandleExcepts := True; ReadData(Stream); finally Stream.Free; end; FPropName := ''; end; end;
在两个方法都将 Name 参数值与当前的属性名比较,如果相同则进行读操作。在 DefineBinaryproperty 中,创建了一个内存流。先将数据读到内存流中然后调用 ReadData 读取数据。 3. FlushBuffer 的实现 FlushBuffer 方法用于清除 Reader 对象的内部缓冲区中的内容,保持 Reader 对象和流在位置( Position )上的同步,其实现如下:
procedure TReader.FlushBuffer; begin FStream.Position := FStream.Position - (FBufEnd - FBufPos); FBufPos := 0; FBufEnd := 0; end;
4. ReadListBegin 、 ReadListEnd 和 EndOfList 方法 这三个方法都是用于从 Reader 对象的流中读取一连串的项目,并且这些项目都由 WriteListBegin 写入的标志标定开始和 WriteListEnd 写入标志,标定结束,在读循环中用 EndOfList 进行判断。它们是在 Reader 对象读取流中数据时经常用于的。它们的实现如下:
procedure TReader.ReadListBegin; begin CheckValue(vaList); end;
procedure TReader.ReadListEnd; begin CheckValue(vaNull); end;
function TReader.EndOfList: Boolean; begin Result := ReadValue = vaNull; Dec(FBufPos); end;
项目表开始标志是 VaList ,项目表结束标志是 VaNull , VaList 和 VaNull 都是枚举类型 TValueType 定义的常量。 它们实现中调用的 CheckValue 是 TReader 的私有方法,其实现如下:
procedure TReader.CheckValue(Value: TValueType); begin if ReadValue <> Value then begin Dec(FBufPos); SkipValue; PropValueError; end; end;
CheckValue 方法的功能是检测紧接着要读的值是否是 Value 指定的类型。如果不是则跳过该项目并触发一个 SInvalidPropertyValue 错误。 EndOfList 函数只是简单地判断下一字节是否是 VaNull 将判断结果返回,并将字节移回原来位置。 5. 简单数据类型读方法的实现 简单数据类型指的是布尔型、字符型、整型、字符串型、浮点型、集合类型和标识符。将它们放在一起介绍是因为它们的实现方法类似。 因为它们的实现都用到了 ReadValue 方法,因此先来介绍 ReadValue 方法的实现:
function TReader.ReadValue: TValueType; begin Read(Result, SizeOf(Result)); end;
该方法调用私有方法 Read ,从 Reader 对象流中读一个字节,并移动位置指针。 ReadValue 方法专门从流中读取值的类型的,所有的数据读写方法中在读取数据前都要调用 ReadValue 方法判断是否是所要读的数据。如果是,则调用 Read 方法读取数据;否则触发一个异常事件,下面看 Integer 类型的读方法:
function TReader.ReadInteger: Longint; var S: Shortint; I: Smallint; begin case ReadValue of vaInt8: begin Read(S, SizeOf(Shortint)); Result := S; end; vaInt16: begin Read(I, SizeOf(I)); Result := I; end; vaInt32: Read(Result, SizeOf(Result)); else PropValueError; end; end;
因为 Delphi 2.0 中,整型可分 8 位、 16 位和 32 位,因此读取整型数据时分别作了判断。 布尔类型的数据是直接放在值类型标志上,如果类型为 VaTrue ,则值为 True ;如果类型为 VaFalse ,则值为 False 。
function TReader.ReadBoolean: Boolean; begin Result := ReadValue = vaTrue; end;
ReadString 方法也利用 ReadValue 方法判断是字符串还是长字符串。
function TReader.ReadString: string; var L: Integer; begin L := 0; case ReadValue of vaString: Read(L, SizeOf(Byte)); vaLString: Read(L, SizeOf(Integer)); else PropValueError; end; SetString(Result, PChar(nil), L); Read(Pointer(Result)^, L); end;
如果 VaString 类型紧接着一个字节存有字符串的长度;如果是 VaLString 类,则紧接着两个字节存放字符串长度,然后根据字符串长度用 SetString 过程给分配空间,用 Read 方法读出数据。 ReadFloat 方法允许将整型值转换为浮点型。
function TReader.ReadFloat: Extended; begin if ReadValue = vaExtended then Read(Result, SizeOf(Result)) else begin Dec(FBufPos); Result := ReadInteger; end; end;
字符类型数据设有直接的标志,它是根据 VaString 后面放一个序值为 1 的字节来判断的。
function TReader.ReadChar: Char; begin CheckValue(vaString); Read(Result, 1); if Ord(Result) <> 1 then begin Dec(FBufPos); ReadStr; PropValueError; end; Read(Result, 1); end;
出于读取 DFM 文件需要, Filer 对象支持读取标识符。
function TReader.ReadIdent: string; var L: Byte; begin case ReadValue of vaIdent: begin Read(L, SizeOf(Byte)); SetString(Result, PChar(nil), L); Read(Result[1], L); end; vaFalse: Result := 'False'; vaTrue: Result := 'True'; vaNil: Result := 'nil'; else PropValueError; end; end;
一般说来,各种复杂的数据结构都是由这些简单数据组成 ; 定义了这些方法等于给读各种类型的数据提供了元操作,使用很方便。例如,读取字符串类型的数据时,如果采用传流方法还要判断字符串的长度,使用 ReadString 方法就不同了。但应该特别注意的是这些类型数据的存储格式是由 Delphi 设计的与简单数据类型有明显的不同。因此,存入数据时应当使用 Writer 对象相应的方法,而且在读数据前要用 NextValue 方法进行判断,否则会触发异常事件。 6. 读取部件的方法的实现 Reader 对象中用于读取部件的方法有 ReadSignature 、 ReadPrefix 、 ReadComponent 、 ReadRootComponent 和 ReadComponents 。 ReadSignature 方法主要用于读取 Delphi Filer 对象标签一般在读取部件前,都要用调用 ReadSignature 方法以指导部件读写过程。
procedure TReader.ReadSignature; var Signature: Longint; begin Read(Signature, SizeOf(Signature)); if Signature <> Longint(FilerSignature) then ReadError(SInvalidImage); end;
FilerSignature 就是 Filer 对象标签其值为“ TPF0 ” ,如果读的不是“ TPF0 ” ,则会触发 SInValidImage 异常事件。 ReadPrefix 方法是用于读取流中部件前的标志位,该标志表示该部件是否处于从祖先窗体中继承的窗体中和它在窗体中的位置是否很重要。
procedure TReader.ReadPrefix(var Flags: TFilerFlags; var AChildPos: Integer); var Prefix: Byte; begin Flags := []; if Byte(NextValue) and $F0 = $F0 then begin Prefix := Byte(ReadValue); Byte(Flags) := Prefix and $0F; if ffChildPos in Flags then AChildPos := ReadInteger; end; end;
TFilerFlags 的定义是这样的:
TFilerFlag = (ffInherited, ffChildPos); TFilerFlags = Set of TFilerFlag;
充当标志的字节的高四位是 $F ,低四位是集合的值,也是标志位的真正含义。如果 ffChildPos 置位,则紧接着的整型数字中放着部件在窗体中的位置序值。 ReadComponent 方法用于从 Reader 对象的流中读取部件。 Component 参数指定了要从流中读取的对象。函数返回所读的部件。
function TReader.ReadComponent(Component: TComponent): TComponent; var CompClass, CompName: string; Flags: TFilerFlags; Position: Integer; …
begin ReadPrefix(Flags, Position); CompClass := ReadStr; CompName := ReadStr; Result := Component; if Result = nil then if ffInherited in Flags then FindExistingComponent else CreateComponent; if Result <> nil then try Include(Result.FComponentState, csLoading); if not (ffInherited in Flags) then SetCompName; if Result = nil then Exit; Include(Result.FComponentState, csReading); Result.ReadState(Self); Exclude(Result.FComponentState, csReading); if ffChildPos in Flags then Parent.SetChildOrder(Result, Position); FLoaded.Add(Result); except if ComponentCreated then Result.Free; raise; end; end;
ReadCompontent 方法首先调用 ReadPrefix 方法,读出部件标志位和它的创建次序值 (Create Order) 。然后用 ReadStr 方法分别读出部件类名和部件名。如果 Component 参数为 nil ,则执行两个任务: ● 如果 ffInberited 置位则从 Root 找已有部件,否则,就从系统的 Class 表中找到该部件类型的定义并创建 ● 如果结果不为空,将用部件的 ReadState 方法读入各种属性值,并设置部件的 Parent 属性,并恢复它在 Parent 部件的创建次序。
ReadComponent 方法主要是调用 ReadComponent 方法从 Reader 对象的流中读取一连串相关联的部件,并分解相互引用关系。
procedure TReader.ReadComponents(AOwner, AParent: TComponent; Proc: TReadComponentsProc); var Component: TComponent; begin Root := AOwner; Owner := AOwner; Parent := AParent; BeginReferences; try while not EndOfList do begin ReadSignature; Component := ReadComponent(nil); Proc(Component); end; FixupReferences; finally EndReferences; end; end;
ReadComponents 首先用 AOwner 和 AParent 参数给 Root,Owner 和 Parent 赋值,用于重建各部件的相互引用。然后用一个 While 循环读取部件并用由 Proc 传入的方法进行处理。在重建引用关系时,用了 BeginReferences 、 FixUpReferences 和 EndReferences 嵌套模式。 ReadRootComponent 方法从 Reader 对象的流中将部件及其拥有的部件全部读出。如果 Component 参数为 nil ,则创建一个相同类型的部件,最后返回该部件:
function TReader.ReadRootComponent(Root: TComponent): TComponent;
function FindUniqueName(const Name: string): string; begin … end;
var I: Integer; Flags: TFilerFlags; begin ReadSignature; Result := nil; try ReadPrefix(Flags, I); if Root = nil then begin Result := TComponentClass(FindClass(ReadStr)).Create(nil); Result.Name := ReadStr; end else begin Result := Root; ReadStr; { Ignore class name } if csDesigning in Result.ComponentState then ReadStr else Result.Name := FindUniqueName(ReadStr); end; FRoot := Result; if GlobalLoaded <> nil then FLoaded := GlobalLoaded else FLoaded := TList.Create; try FLoaded.Add(FRoot); FOwner := FRoot; Include(FRoot.FComponentState, csLoading); Include(FRoot.FComponentState, csReading); FRoot.ReadState(Self); Exclude(FRoot.FComponentState, csReading); if GlobalLoaded = nil then for I := 0 to FLoaded.Count - 1 do TComponent(FLoaded[I]).Loaded; finally if GlobalLoaded = nil then FLoaded.Free; FLoaded := nil; end; GlobalFixupReferences; except RemoveFixupReferences(Root, ''); if Root = nil then Result.Free; raise; end; end;
ReadRootComponent 首先调用 ReadSignature 读取 Filer 对象标签。然后在 try … except 循环中执行读取任务。如果 Root 参数为 nil ,则用 ReadStr 读出的类名创建新部件,并以流中读出部件的 Name 属性;否则,忽略类名,并判断 Name 属性的唯一性。最后用 Root 的 ReadState 方法读取属性和其拥有的拥有并处理引用关系。 7. SetName 方法和 OnSetName 事件 因为在 OnSetName 事件中, Name 参数是 var 型的,所以可以用 OnSetName 事件处理过程修改所读部件的名字。而 OnSetName 事件处理过程是在 SetName 方法中实现的。
procedure TReader.SetName(Component: TComponent; var Name: string); begin if Assigned(FOnSetName) then FOnSetName(Self, Component, Name); Component.Name := Name; end;
SetName 方法和 OnSetName 事件在动态 DFM 文件的编程中有很重要的作用。 8. TReader 的错误处理 TReader 的错误处理是由 Error 方法和 OnError 事件的配合使用完成的。 OnError 事件处理过程的 Handled 参数是 var 型的布尔变量,通过将 Handled 设为 True 或 False 可影响 TReader 的错误处理。 OnError 事件处理过程是在 Error 方法中调用的。
function TReader.Error(const Message: string): Boolean; begin Result := False; if Assigned(FOnError) then FOnError(Self, Message, Result); end;
9. FindMethod 和 OnFindMethod 事件 有时,在程序运行期间,给部件的方法指针 ( 主要是事件处理过程 ) 动态赋值是很有用的,这样就能动态地改变部件响应事件的方式。在流中读取部件捍做到一点就要利用 OnFindMehtod 事件。 OnFIndMethod 事件是在 FindMethod 方法中被调用的。
function TReader.FindMethod(Root: TComponent; const MethodName: string): Pointer; var Error: Boolean; begin Result := Root.MethodAddress(MethodName); Error := Result = nil; if Assigned(FOnFindMethod) then FOnFindMethod(Self, MethodName, Result, Error); if Error then PropValueError; end;
OnFindMethod 方法除了可以给部件的 MethodName 所指定的方法指针动态赋值外,还可修改 Error 参数来决定是否处理 Missing Method 错误。方法中调用的 MehtodAddress 方法定义在 TObject 中,它是个很有用的方法,它可以得到对象中定义的 public 方法的地址。 FindMethod 方法和 OnFindMethod 事件在动态 DFM 的编程中有很重要的作用。
20.3 Delphi 对象式数据管理应用实例
Delphi 2.0 无论是其可视化设计工具,还是可视化部件类库 (VCL) ,都处处渗透了对象存储技术,本节将从 Delphi 可视化设计内部机制、 VCL 中的数据存储、 BLOB 数据操作和动态生成部件的存储几方面介绍对象存储功能的实例应用。
20.3.1 Delphi 动态 DFM 文件及部件的存取在超媒体系统中的应用
Delphi 的可视化设计工具是同其部件类库紧密结合在一起的。 每个部件只有通过一段注册程序并通过 Delphi 的 Install Component 功能,才能出现在 Component Palette 上;部件的属性才有可能出现在 Object Inspector 窗口中;部件的属性编辑器才能被 Delphi 环境使用。因为这种浑然天成的关系, DFM 文件存取必然得到 VCL 在程序上的支持。 DFM 文件的部件存取是 Delphi 可视化设计环境中文件存取的中心问题。因为 Delphi 可视化设计的核心是窗体的设计。每个窗体对应一个库单元,是应用程序的模块,窗体在磁盘上的存储就是 DFM 文件。 DFM 文件结构我们前面介绍过了。它实际上是存储窗体及其拥有的所有部件的属性。这种拥有关系是递归的。问题在于如何将这些属性数据与程序中的变量 ( 属性 ) 代码联系起来。 在 Delphi 中处理这种联系的过程分为两种情况:设计时和运行时。 在设计时,建立联系表现为读取 DFM 文件,建立 DFM 文件中的部件及其属性与可视化设计工具 (Object Inspector 、窗体设计窗口和代码编辑器 ) 的联系,也就是说让这些部件及其属性能出现在这些窗口中,并与代码中的属性定义联系起来;分解联系表现为存储 DFM 文件,将窗体窗口中的部件及其属性写入 DFM 文件。 在运行时,主要是建立联系的过程,即读取 DFM 文件。这时, DFM 文件不是作为独立的磁盘文件,而是以应用程序资源中的 RCDATA 类型的二进制数据存在。建立联系的过程表现为将资源中的部件及其属性与应用程序中的对象及其数据域联系起来。其过程为:根据 DFM 中的部件类名创建对象,再将用 DFM 中的部件属性值给程序中的部件属性赋值。当然要完成这一过程,还必须在代码中有相应的窗体定义,因为方法等代码是不存入部件的。 VCL 对读取 DFM 文件在代码上的支持是通过 Stream 对象和 Filer 对象达到的。在 20. 1 和 20.1 节中,我们可以看到 Stream 对象和 Filer 对象中有大量的用于存取部件及其属性的方法,尤其在 TReader 对象中,还有关于错误处理和动态的方法赋值的方法。下面我们就通过程序实例介绍存取 DFM 文件方法、步骤和注意事项。 |