DELPHI基础教程
第二十章 开发Delphi对象式数据管理功能(五)
20.3.1.1 写 DFM 文件的过程: WriteComponentResFie 该过程带有两个参数 FileName 和 Instance 。 FileName 参数指定要写入的 DFM 文件名, Instance 参数是 TComponent 类型的,它指定要写入的部件名,一般是 TForm 对象的子类。该过程将 Instance 部件和其拥有的所有部件写入 DFM 文件。 这个过程的意义在于,可以在程序运行过程中产生 Delphi 的窗体部件和在窗体中插入部件,并由该函数将窗体写入 DFM 文件,支持了动态 DFM 文件的重用性。 该过程的程序是这样的:
procedure WriteComponentResFile(const FileName: string; Instance: TComponent); var Stream: TStream; begin Stream := TFileStream.Create(FileName, fmCreate); try Stream.WriteComponentRes(Instance.ClassName, Instance); finally Stream.Free; end; end;
函数中,用 FileStream 创建文件,用 Stream 对象的 WriteComponetRes 方法将 Instance 写入流中。
20.3.1.2 读 DFM 文件的函数: ReadComponentResFile
ReadComponentResFile 函数带有两个参数 FileName 和 Instance 。 FileName 参数指定要读 DFM 文件名, Instance 参数指定从 DFM 文件中要读的部件。该函数从 DFM 文件中将 Instance 和它拥有的所有部件,并返回该部件。 这个函数的意义在于,配合 WriteComponentResFile 过程的使用支持 DFM 文件的重用性。 该函数的程序是这样的:
function ReadComponentResFile(const FileName: string; Instance: TComponent): TComponent; var Stream: TStream; begin Stream := TFileStream.Create(FileName, fmOpenRead); try Result := Stream.ReadComponentRes(Instance); finally Stream.Free; end; end;
程序中使用 FileStream 对象打开由 FileName 指定的 DFM 文件,然后用 Stream 对象的 ReadComponentRes 方法读出 Instance ,并将读的结果作为函数的返回值。
20.3.1.3 读取 Delphi 应用程序资源中的部件
函数 InternalReadComponentRes 可以读取 Delphi 应用程序资源中的部件。 Delphi 的 DFM 文件在程序经过编译链接后被嵌入应用程序的资源中,而且格式发生了改变,即少了资源文件头。 在第一节中曾经介绍过 TResourceStream 对象,该对象是操作资源媒介上的数据的。函数 InternalReadComponentRes 用了 TResourceStream 。程序是这样的:
function InternalReadComponentRes(const ResName: string; var Instance: TComponent): Boolean; var HRsrc: THandle; begin { 避免“ EResNotFound ”异常事件的出现 } HRsrc := FindResource(HInstance, PChar(ResName), RT_RCDATA); Result := HRsrc <> 0; if not Result then Exit; FreeResource(HRsrc); with TResourceStream.Create(HInstance, ResName, RT_RCDATA) do try Instance := ReadComponent(Instance); finally Free; end; Result := True; end;
HInstance 是一个 Delphi VCL 定义的全局变量,代表当前应用程序的句柄。函数用了资源访问 API 函数 FindResource 来测定是否存在 ResName 所描述资源。因为在 TResourceStream 的创建过程还有 FindResource 等操作,所以函数中调用了 FreeResource 。最后函数调用了 Stream 对象的 ReadComponent 方法读出部件。因为函数的 Instance 是 var 类型的参数,所以可以访问 Instance ,得到读出的部件。
20.3.1.4 DFM 文件与标准文本文件 (TXT 文件 ) 的相互转换
在 Delphi 可视化设计环境中,允许程序员在代码编辑器中以文本的方式浏览和修改 DFM 文件内容。当用 File/Open 命令直接打开 DFM 文件或者选择窗体设计窗口的弹出式菜单上的 View as Text 命令时,就会在编辑器中出现文本形式的信息。我们姑且将这种文本形式称之为窗体设计脚本。 Delphi 提供的这种脚本编辑功能是对 Delphi 可视化设计的一大补充。当然这个脚本编辑能力是有限制的,比方说不能在脚本任意地添加和删除部件,因为代码和 DFM 脚本是紧密相连的,任意添加和修改会导致不一致性。然而在动态生成的 DFM 文件中,就不存在这一限制,后面会介绍 DFM 动态生成技术的应用。 实际上, DFM 文件内容是二进制数据,它的脚本是经过 Delphi 开发环境自动转化的,而且 Delphi VCL 中的 Classes 库单元中提供了在二进制流中的文件 DFM 和它的脚本之相互转化的过程。它们是 ObjectBinaryToText 和 ObjectTextBinary 、 ObjectResourceToText 和 ObjectTextToResource 。 ObjectBinaryToText 过程将二进制流中存储的部件转化为基于文本的表现形式,这样就可以用文本处理函数进行处理,还可以用文本编辑器进行查找和替代操作,最后可以将文本再转化成二进制流中的部件。 ObjectBinaryToText 过程的主程序是这样的:
procedure ObjectBinaryToText(Input, Output: TStream); var NestingLevel: Integer; SaveSeparator: Char; Reader: TReader; Writer: TWriter;
procedure WriteIndent; const Blanks: array[0..1] of Char = ' '; var I: Integer; begin for I := 1 to NestingLevel do Writer.Write(Blanks, SizeOf(Blanks)); end;
procedure WriteStr(const S: string); begin Writer.Write(S[1], Length(S)); end;
procedure NewLine; begin WriteStr(#13#10); WriteIndent; end;
procedure ConvertHeader; begin … end;
procedure ConvertBinary; begin … end;
procedure ConvertValue; begin … end;
procedure ConvertProperty; begin … end;
procedure ConvertObject; begin … end;
begin NestingLevel := 0; Reader := TReader.Create(Input, 4096); SaveSeparator := DecimalSeparator; DecimalSeparator := '.'; try Writer := TWriter.Create(Output, 4096); try Reader.ReadSignature; ConvertObject; finally Writer.Free; end; finally DecimalSeparator := SaveSeparator; Reader.Free; end; end;
过程中调用的 ConvertObject 过程是个递归过程,用于将 DFM 文件中的每一个部件转化为文本形式。因为由于部件的拥有关系,所以部件成嵌套结构,采用递归是最好的方式:
procedure ConvertObject; begin ConvertHeader; Inc(NestingLevel); while not Reader.EndOfList do ConvertProperty; Reader.ReadListEnd; while not Reader.EndOfList do ConvertObject; Reader.ReadListEnd; Dec(NestingLevel); WriteIndent; WriteStr('end'#13#10); end;
NestStingLevel 变量表示部件的嵌套层次。 WriteIndent 是写入每一行起始字符前的空格, ConvertHeader 过程是处理部件的继承标志信息。转换成的头信息文本有两种形式。 Inherited TestForm1: TTestForm[2] 或者: Object TestForm1: TTestForm
前者是 ffInherited 和 ffChildPos 置位,后面是都没置位。 ConvertProperty 过程用于转化属性。
procedure ConvertProperty; begin WriteIndent; WriteStr(Reader.ReadStr); WriteStr(' = '); ConvertValue; WriteStr(#13#10); end;
WriteIndent 语句写入属性名前的空格, WriteStr(Reader.ReadStr) 语句写入属性名 ConvertValue 过程根据属性的类型将属性值转化为字符串,然后写入流中。 ObjectTextToBinary 过程执行的功能与 ObjectBinaryToText 相反,将 TXT 文件转换为二进制流中的部件,而且只要 TXT 文件内容的书写符合 DFM 脚本语法, ObjectTextToBinary 可将任何程序生成的 TXT 文件转换为部件,这一功能也为 DFM 文件的动态生成和编辑奠定了基础。 ObjectTextToBinary 过程的主程序如下:
procedure ObjectTextToBinary(Input, Output: TStream); var SaveSeparator: Char; Parser: TParser; Writer: TWriter;
…
begin Parser := TParser.Create(Input); SaveSeparator := DecimalSeparator; DecimalSeparator := '.'; try Writer := TWriter.Create(Output, 4096); try Writer.WriteSignature; ConvertObject; finally Writer.Free; end; finally DecimalSeparator := SaveSeparator; Parser.Free; end; end;
在程序流程和结构上与 ObjectBinaryToText 差不多。 ConvertObject 也是个递归过程:
procedure ConvertObject; var InheritedObject: Boolean; begin InheritedObject := False; if Parser.TokenSymbolIs('INHERITED') then InheritedObject := True else Parser.CheckTokenSymbol('OBJECT'); Parser.NextToken; ConvertHeader(InheritedObject); while not Parser.TokenSymbolIs('END') and not Parser.TokenSymbolIs('OBJECT') and not Parser.TokenSymbolIs('INHERITED') do ConvertProperty; Writer.WriteListEnd; while not Parser.TokenSymbolIs('END') do ConvertObject; Writer.WriteListEnd; Parser.NextToken; end;
DFM 文件与 DFM 脚本语言之间相互转换的任务由 ObjectResourceToText 和 ObjextTextToResource 两个过程完成。
procedure ObjectResourceToText(Input, Output: TStream); begin Input.ReadResHeader; ObjectBinaryToText(Input, Output); end;
ObjectTextToResource 过程就比较复杂,因为 DFM 文件资源头中要包含继承标志信息,因此在调用 ObjectTextToBinary 后,就读取标志信息,然后写入资源头。
procedure ObjectTextToResource(Input, Output: TStream); var Len: Byte; Tmp: Longint; MemoryStream: TMemoryStream; MemorySize: Longint; Header: array[0..79] of Char; begin MemoryStream := TMemoryStream.Create; try ObjectTextToBinary(Input, MemoryStream); MemorySize := MemoryStream.Size; FillChar(Header, SizeOf(Header), 0); MemoryStream.Position := SizeOf(Longint); { Skip header } MemoryStream.Read(Len, 1); if Len and $F0 = $F0 then begin if ffChildPos in TFilerFlags((Len and $F0)) then begin MemoryStream.Read(Len, 1); case TValueType(Len) of vaInt8: Len := 1; vaInt16: Len := 2; vaInt32: Len := 4; end; MemoryStream.Read(Tmp, Len); end; MemoryStream.Read(Len, 1); end; MemoryStream.Read(Header[3], Len); StrUpper(@Header[3]); Byte((@Header[0])^) := $FF; Word((@Header[1])^) := 10; Word((@Header[Len + 4])^) := $1030; Longint((@Header[Len + 6])^) := MemorySize; Output.Write(Header, Len + 10); Output.Write(MemoryStream.Memory^, MemorySize); finally MemoryStream.Free; end; end;
20.3.1.5 动态 DFM 文件应用揭秘
1. 动态 DFM 文件概述 动态 DFM 文件是相对于静态 DFM 文件而言。所谓静态 DFM 文件是指在 Delphi 开发环境中设计的窗体文件。窗体的设计过程就是程序的编制过程。因此,动态 DFM 文件就是指在程序运行过程生成或存取的 DFM 文件。 动态 DFM 文件的创建和使用分别如下两种情况: ● 在程序运行过程中,由 Create 方法动态生成窗体或部件,然后动态生成其它部件插入其中生成 DFM 文件 ● 在 Delphi 开发环境中,设计生成 DFM 文件,然后用 DFM 文件存取函数,或者用 Stream 对象和 Filer 对象的方法,将 DFM 文件读入内存,进行处理,最后又存入磁盘中
由 Delphi 的窗体设计的常规方法生成的 DFM 文件在程序运行一开始就规定了部件的结构。因为在窗体设计过程中,窗体中的每个部件都在程序的对象声明中定义了部件变量。这种固定的结构虽然能方便应用,但以牺牲灵活性为代价。 在 Delphi 应用程序中有时需要在运行过程中创建控制,然后将该控制插入另一个部件中。例如:
procedure TForm1.Button1Click(Sender: Tobject); var Ctrl: TControl begin Ctrl := TEdit.Create(Self); Ctrl.Top := 100; Ctrl.Left := 100; Ctrl.Width := 150; Ctrl.Height := 20; InsertControl(Ctrl); end;
动态插入控制的优点是可以在任何时刻、任意位置插入任意数量的任何类型的控制。因为应用程序需求在很多情况下是在程序运行中才知道的,所以动态插入控制就显得很重要。而且在很多情况下,需要保存这些界面元素,留待程序再次调用。例如应用程序界面的定制、系统状态的保存、对话框的保存等。这时生成动态 DFM 文件是最佳选择。 动态插入控制的不足之处是在插入控制前,无法直观地看到控制的大小、风格、位置等,也就是动态插入控制的过程是非可视化的。但可以借助于静态 DFM 文件的可视化设计。这就是生成和使用动态 DFM 文件的第二种方法。也就是在应用程序运行前,在 Delphi 开发环境中,使用可视化开发工具设计所需窗口或部件的样式,以 DFM 文件保存。然后在应用程序运行过程中,将 DFM 文件读入内存。 Delphi 的 Stream 对象和 Filer 对象在读取 DFM 文件时,会根据 DFM 文件的内容自动创建部件及其拥有的所有部件。 在使用动态 DFM 文件时有两点需要注意。 ● 每一个动态插入的控制或部件必须在程序中调用 RegisterClass 进行注册 ● 读入 DFM 文件自动创建部件后,如果调用了 InsertControl 方法, 则在关闭窗口时要调用 RemoveControl 方法移去该控制,否则会产生异常事件
2. 动态 DFM 文件应用之一:超媒体系统的卡片设计 Delphi 多种类型的可视部件,如文本部件、编辑部件、图形图像部件、数据库部件、媒体媒放部件和 OLE 部件等,每一种部件在屏幕中占据一定的区域,具有相当丰富的表现能力,可以作为卡片中的一种媒体,因此可以利用这些可视部件进行超媒体系统的卡片设计。 超媒体卡片设计要求卡片中的媒体数目和媒体种类是不受限制的,而且必须能够修改和存取卡片,因此,采用动态 DFM 文件是比较合适的。而且如果利用 Stream 对象,将卡片存储在数据库 BLOB 字段中,就为把超文本与关系数据库技术结合起来创造了契机。 下面是超媒体卡片设计子系统中的部分源程序,它演示了如何创建对象、插入对象和存取动态 DFM 文件。 ⑴ 在应用程序中注册对象
procedure TMainForm.FormCreate(Sender: TObject); begin RegisterClass(TLabel); RegisterClass(TEdit); RegisterClass(TMemo); RegisterClass(TButton); RegisterClass(TPanel); RegisterClass(TPanelP); RegisterClass(TBitBtn); … end;
⑵ 创建和插入对象
procedure TMDIChild.FormClick(Sender: TObject); var Ctrl : TControl; Point: TPoint; begin GetCursorPos(Point); Point := BackGround.ScreenToClient(Point); case CurToolIndex of 1 : begin Ctrl := TLabel.Create(self); TLabel(Ctrl).AutoSize := False; TLabel(ctrl).Caption := 'Label'+S; TLabel(ctrl).Name := 'Label 1'; TLabel(ctrl).Top := Point.Y; TLabel(ctrl).Left := Point.X; TLabel(Ctrl).Height := Round(100*Res/1000/Ratio); TLabel(Ctrl).Width := Round(600*Res/1000/Ratio); TLabel(Ctrl).Color := clWhite; TLabel(Ctrl).Font.Color := clBlack; TLabel(Ctrl).Font.Name := 'Roman'; TLabel(Ctrl).Font.Height := -TLabel(Ctrl).Height; TLabel(Ctrl).Font.Pitch := fpFixed; TLabel(Ctrl).Enabled := False; TLabel(Ctrl).OnClick := LabelClick; TLabel(Ctrl).OnMouseMove := ReportPos; BackGround.InsertControl(Ctrl); CurTool.Down := False; CurTool := nil; … end; 2: begin Ctrl := TEdit.Create(self); TEdit(ctrl).AutoSize := True; TEdit(ctrl).Top := Point.Y; TEdit(ctrl).Left := Point.X; TEdit(Ctrl).Height := 20; BackGround.InsertControl(Ctrl); … end; 3: … end; end;
⑵ 存取动态 DFM 文件
procedure TMainForm.FileOpen(Sender: TObject); begin if OpenDialog.Execute then begin DesignWin := TMDIChild.Create(Application); ReadComponentResFile(OpenDialog.FileName, DesignWin); DesignWin.Init; FileName := OpenDialog.FileName; DesignWin.Caption := FFileName; end; end;
DesignWin 是在 TMainForm 中定义的 TMDIChild 类型的窗体部件,是卡片设计平台; FFileName 是私有变量,用来保存当前编辑的卡片文件名。 DesignWin 的 Init 方法实现如下:
procedure TMDIChild.Init; var I: Integer; Ctrl: TControl; begin BackGround.BringToFront; with BackGround do for I:= 0 to ControlCount - 1 do if Controls[I].Name <> ''then ObjectIns.ObjectList.Items.AddObject(Controls[I].Name, Controls[I]); end;
BackGround 是 TPanel 类型的部件,所有的动态创建对象都插入到 BackGround 中,所以,后面调用 BackGround.InsertControl(Ctrl) ; ObjectIns 是个仿 Delphi 的媒体属性编辑器。 动态 DFM 文件的存储过程是这样的:
procedure TMainForm.FileSave(Sender: TObject); begin if DesignWin.CurControl <> nil then DesignWin.CurControl.Enabled := True; WriteComponentResFile(FFilename, DesignWin); DesignWin.Caption := FileName; end; end;
因为在 DesignWin 的 Init 方法中调用了 InsertControl 方法,所以在关闭 DesignWin 窗口时要相应地调用 RemoveControl ,否则在关闭 DesignWin 窗口时会产生内存错误。
procedure TMDIChild.FormCloseQuery(Sender: TObject; var CanClose: Boolean); var I: Integer; Ctrl: TControl; Removed: Boolean; begin if Modified = True then if MessageDlg('Close the form?', mtConfirmation, [mbOk, mbCancel], 0) = mrCancel then CanClose := False; if CanClose = True then begin repeat removed := False; I := 0; repeat if BackGround.Controls[I].Name <> '' then begin BackGround.RemoveControl(BackGround.Controls[I]); Removed := True; end; I := I + 1 until (I >= BackGround.ControlCount) or (Removed = True); until (Removed = False); SendMessage(ObjectIns.Handle, WM_MDICHILDCLOSED, 0, 0); end; end;
3. 动态 DFM 文件应用之二:超媒体系统脚本语言设计 超媒体脚本语言设计是超媒体系统设计的重要内容。脚本语言必须能够表达卡片中的多种媒体对象,必须是可编程,可理解的,必须是可执行的,应该可以由脚本语言生成超媒体系统中的卡片和链。 DFM 文件可以看作是超媒体系统的卡片, DFM 脚本能够表达 DFM 文件中的多种控制,也就是说能够表达卡片中的多种媒体对象,再加上 DFM 脚本的对象式表达,可编辑性,可转换为 DFM 文件,因此用作超媒体系统脚本语言较好的形式。 ObjectBinaryToText 和 ObjectTextToBinary 过程提供了在部件和 DFM 脚本之间相互转化的功能, ObjectResourceToText 和 ObjectTextToResoure 过程提供了 DFM 文件和 DFM 脚本之间相互转化的功能。这样就可以在应用程序中自如实现超媒体卡片和超媒体脚本语言相互转化。
下面是卡片和脚本语言相互转化的程序:
procedure TMDIChild.CardToScript; var In, Out: TStream; begin In := TMemoryStream.Create; Out := TMemoryStream.Create; try In.WriteComponentRes(Self.ClassName, Self); ObjectResourceToText(In, out); ScriptForm.ScriptEdit.Lines.LoadFromStream(Out); finally In.Free; Out.Free; end; end;
ScriptEdit 是个文本编辑器,它的 Lines 属性是 TStrings 类型的对象。
procedure TScriptForm.ScriptToCard; var In, Out: TStream; begin In := TMemoryStream.Create; Out := TMemoryStream.Create; try ScriptForm.ScriptEdit.Lines.SaveToFromStream(In); ObjectTextToResource(In, out); In.ReadComponentRes(DesignWin); finally In.Free; Out.Free; end; end;
这两段程序是对整个卡片,即窗体级,进行转换的。 ObjectBinaryToText 和 ObjectTextToBinary 过程可以细化到部件级的转换。因此超媒体脚本语言的编辑可以细化到媒体对象级。 4. 超媒体编辑和表现系统与动态 DFM 文件的扩展 超媒体系统的媒体编辑与卡片管理有其特殊的需求,比如链接需求。这时采用已有的窗体部件和媒体部件并按常规的 DFM 文件处理就显得力不从心了。解决这个矛盾有两套方案: ● 利用 Delphi 部件开发技术,继承和开发新的部件增加新的超媒体特有的属性和处理方法 ● 扩展 DFM 文件结构,使之能按自己的需要任意地存取和转换部件和 DFM 文件
前者是充分利用 Delphi 的面向对象部件开发技术,在存取和转换等处理上仍旧与常规 DFM 文件相同。而后者需要 DFM 的存取和转换上作比较大的改动。下文介绍扩展 DFM 文件的思路。 扩展动态 DFM 文件的总体思路是降低处理操作的数据的颗粒度,即从原先窗体级降低到部件级。 下面是存取操作的扩展示范:
var FileStream: TStream; I: Integer; begin FileStream := TFileStream.Create('OverView.Crd', fmOpenWrite); With TWriter.Create(FileStream, 4096) do try for I := 0 to DesignWin.ControlCount - 1 do begin WriteInteger(MMID[i]); WriteRootComponent(DesignWin.Controls[i]); { 写相应媒体扩展信息 } …… end; WriteListEnd; finally. Free; end; FileStream.Free; end;
WriteInteger(MMID[i]) 语句是写入媒体标识。 下面是相应的读扩展 DFM 的程序:
var PropInfo: PPropInfo; Method : TMethod; FileStream: TStream; I: Integer; begin FileStream := TFileStream.Create('OverView.Crd', fmOpenRead); With TReader.Create(FileStream, 4096) do try while not EndOfList do begin case ReadInteger of IDText: begin Ctrl := TControl(ReadRootComponent(nil)); PropInfo := GetPropInfo(Ctrl.ClassInfo, 'OnClick'); Method.Code:= Self.MethodAddress(MethodName); Method.Data := Self; if Method.Code <> nil then SetMethodProp(Ctrl, PropInfo, Method); DesignWin.InsertControl(Ctrl); end; IDImage: …… end; …… WriteListEnd; end; finally. Free; end; FileStream.Free; end;
SetMethodProp 过程是用于重新联接控制和它的事件处理过程。类似的功能还可以用 TReader 对象的 OnFindMethod 事件的处理过程来实现。 实现脚本语言扩展的基本方法与存取扩展类似,但它还要加扩展媒体信息转换为文本,并插入到部件的脚本描述中。
20.3.2 数据库 BLOB 字段应用
Delphi VCL 提供了 TBlobStream 对象支持对数据库 BLOB 字段的存取。 Delphi 的 TBlobStream 对象的作用在于一方面可以使 Delphi 应用程序充分利用多媒体数据库的数据管理能力。另一方面又能利用 Delphi Object Pascal 的程序设计能力给关系型多媒体数据库提供底层控制能力和全方位的功能扩展余地。
20.3.2.1 TBlobStream 的使用
TBlobStream 对象用一个 TBlobField 类型的对象作为参数来创建与 BLOB 字段相联的 BLOB 流,接着就可用流的存取方法在 BLOB 字段中存取数据。
var BlobStream: TBlobStream; I: Integer; begin BlobStream := TBlobStream.Create(TBlobField(CardTable.Fields[10], bmWrite); With TWriter.Create(BlobStream, 4096) do try for I := 0 to DesignWin.ControlCount - 1 do begin WriteInteger(MMID[i]); WriteRootComponent(DesignWin.Controls[i]); { 写相应媒体扩展信息 } …… end; WriteListEnd; finally. Free; end; BlobStream.Free; CardTable.Post; end;
Fields 变量是表示数据库记录的字段数组, Fields[10] 正是数据库的 BLOB 字段。 CardTable 的 Post 方法将数据库的修改反馈到数据库的物理存储上。 上面这段程序是超媒体卡片存储的部分源程序,我们就是将卡片保存在数据库 BLOB 字段中,实现将超文本和关系数据库两种数据管理方式结合起来。读卡片的程序如下:
var PropInfo: PPropInfo; Method: TMethod; Blobtream: TStream; I: Integer; begin BlobStream := TBlobStream.Create(TBlobField(CardTable.Fields[10]), bmRead); With TReader.Create(BlobStream, 4096) do try while not EndOfList do begin case ReadInteger of IDText: begin Ctrl := TControl(ReadRootComponent(nil)); PropInfo := GetPropInfo(Ctrl.ClassInfo, 'OnClick'); Method.Code:= Self.MethodAddress(MethodName); Method.Data := Self; if Method.Code <> nil then SetMethodProp(Ctrl, PropInfo, Method); DesignWin.InsertControl(Ctrl); end; IDImage: …… end; …… WriteListEnd; end; finally. Free; end; FileStream.Free; end;
20.3.2.2 BLOB 字段与图形图像
在多媒体数据库中处理得比较多的是图形图像,因此早期的多媒体数据库在扩展关系数据库时往往是增加一个图像字段。 BLOB 字段是以二进制数据存储方式,因此它完全可以表达图形图像数据。 在 TBlobField 对象中提供了 LoadFromBitMap 和 SaveToBitMap 方法存取位图数据。它们在实现上都是使用 BlobStream 对象。
procedure TBlobField.LoadFromBitmap(Bitmap: TBitmap); var BlobStream: TBlobStream; Header: TGraphicHeader; begin BlobStream := TBlobStream.Create(Self, bmWrite); try if (DataType = ftGraphic) or (DataType = ftTypedBinary) then begin Header.Count := 1; Header.HType := $0100; Header.Size := 0; BlobStream.Write(Header, SizeOf(Header)); Bitmap.SaveToStream(BlobStream); Header.Size := BlobStream.Position - SizeOf(Header); BlobStream.Position := 0; BlobStream.Write(Header, SizeOf(Header)); end else Bitmap.SaveToStream(BlobStream); finally BlobStream.Free; end; end;
procedure TBlobField.SaveToBitmap(Bitmap: TBitmap); var BlobStream: TBlobStream; Size: Longint; Header: TGraphicHeader; begin BlobStream := TBlobStream.Create(Self, bmRead); try Size := BlobStream.Size; if Size >= SizeOf(TGraphicHeader) then begin BlobStream.Read(Header, SizeOf(Header)); if (Header.Count <> 1) or (Header.HType <> $0100) or (Header.Size <> Size - SizeOf(Header)) then BlobStream.Position := 0; end; Bitmap.LoadFromStream(BlobStream); finally BlobStream.Free; end; end;
程序中按两种方式存取数据,对于位图数据,数据的起点是流的 Potition 为 0 处,对于图形或其它类型的 Blob 数据,则以流的 Position 为 SizeOf(Header) + 1 处开始, 即多了个头信息。
20.3.2.3 BLOB 字段与文本
Delphi BLOB 字段中增加了大型文本的处理能力。可以在 TBlobField 和 Strings 中自由地交换数据。
procedure TBlobField.LoadFromStrings(Strings: TStrings); var BlobStream: TBlobStream; begin BlobStream := TBlobStream.Create(Self, bmWrite); try Strings.SaveToStream(BlobStream); finally BlobStream.Free; end; end;
procedure TBlobField.SaveToStrings(Strings: TStrings); var BlobStream: TBlobStream; begin BlobStream := TBlobStream.Create(Self, bmRead); try Strings.LoadFromStream(BlobStream); finally BlobStream.Free; end; end;
20.3.2.4 BLOB 字段与 Stream 对象
因为 Delphi 中, BLOB 字段是通过 BLOB 流来访问的,所以可以很容易地在 BLOB 字段和 Stream 对象之间传递数据。为此, TBlobField 对象提供了 LoadFromStream 和 SaveToStream 方法。
procedure TBlobField.LoadFromStream(Stream: TStream); var BlobStream: TBlobStream; begin BlobStream := TBlobStream.Create(Self, bmWrite); try BlobStream.CopyFrom(Stream, 0); finally BlobStream.Free; end; end;
procedure TBlobField.SaveToStream(Stream: TStream); var BlobStream: TBlobStream; begin BlobStream := TBlobStream.Create(Self, bmRead); try Stream.CopyFrom(BlobStream, 0); finally BlobStream.Free; end; end;
20.3.3 存取嵌入在 OleContainer 对象中的 OLE 服务器的数据
对象链接和嵌入 (Object Linking and Embedding ,简称 OLE) ,是一组服务功能,它提供了一种用来源于不同应用程序的信息创建复合文档的强有力方法。 通过把图像、图形、表格、声音、注解、文件和其它表示手段描述成对象,用它能在不同软件厂家提供的应用程序中更为容易地交换合成和处理数据它是应用程序的集成更为容易。 OLE2.0 支持直观编辑。用户不需切换到不同窗口就能在文档中直接对对象进行操作,改进了操作环境。用户不用再关注应用程序和操作环境,只需关注于使用对象技术的数据和文件,便能完成全部工作。 OLE 已成为操作系统功能上的一大标准,各大软商纷纷在开发工具中支持 OLE 2.0 规范。 Delphi 2.0 提供了 OleContainer 对象支持 OLE 窗户应用程序的开发。 尽管通过 OLE 可以用来源于不同应用程序的信息创建复合文档,充分体现以任务、以文档为中心的思想,但是很难分解来自其它应用程序中的嵌入数据,以进行特殊的处理。 例如,一套多媒体电子文档管理系统,系统需要数据库管理功能文档编辑功能,全文检索功能等。在文档编辑功能的实现上,如果能利用中文 Word 或写字板之类的强大的编辑排版功能,就可以省却重新开发一个文档编辑的费用,使用具有直观编辑的 OLE 复合文档嵌入 Word 的 DOC 数据或 RTF 数据当然是最佳的选择。 但问题在于全文检索系统要求能直接在文档中搜索关键字,因此要求将文档数据从 OLE 嵌入数据或文档中的本地数据中分离出来。 Delphi 2.0 的 OleContainer 部件支持存储 OLE 对象数据。 OLE 对象数据包括两部分: OLE 类描述信息和 OLE 服务器嵌入数据。一般说来, OLE 服务器嵌入数据是以服务器支持的数据格式存储的 ; 比方说,中文 Word 6.0 的嵌入数据的格式就是 Word 6.0 文档的格式。因此,要将文档数据从 OLE 嵌入式文档中分离出来就是要访问第二部分数据。 我们分析了 Delphi 2.0 的 OleContainer 对象存取复合文档的程序,得到分离数据的方法。 让我们来看一段 OleContainer 对象存储数据的程序:
procedure TOleContainer.SaveToStream(Stream: TStream); var DataHandle: HGlobal; Buffer: Pointer; Header: TStreamHeader; R: TRect; …… begin …… try …… if FOldStreamFormat then begin R := BoundsRect; Header.PartRect.Left := R.Left; Header.PartRect.Top := R.Top; Header.PartRect.Right := R.Right; Header.PartRect.Bottom := R.Bottom; end else begin Header.Signature := StreamSignature; Header.DrawAspect := FDrawAspect; end; Header.DataSize := GlobalSize(DataHandle); Stream.WriteBuffer(Header, SizeOf(Header)); Buffer := GlobalLock(DataHandle); try Stream.WriteBuffer(Buffer^, Header.DataSize); finally GlobalUnlock(DataHandle); end; finally ReleaseObject(TempStorage); ReleaseObject(TempLockBytes); end; end;
程序中, OleContainer 对象执行了两次往流中写数据的操作。 Stream.WriteBuffer(Header, Size(Header)); Stream.WriteBuffer(Buffer^, Header.DataSize);
前一语句是写入 OLE 类描述信息,后一句语句是写入 OLE 服务器的嵌入数据。 Header 是 TStreamHeader 记录类型的变量。 TStreamHeader 记录的定义如下:
TStreamHeader = record case Integer of 0: ( { 新版 OLE 对象 } Signature: Integer; DrawAspect: Integer; DataSize: Integer); 1: ( { 旧版 OLE 对象 } PartRect: TSmallRect); end;
因此读 OLE 服务器嵌入数据时,要跳过文件头的 TStreamHeader 记录。下面就是如何分离 OLE 服务器嵌入数据的程序:
var Stream : TMemoryStream; FileStream : TFileStream; begin Stream := TMemoryStream.Create; FileStream := TFileStream.Create('TEST.DOC', fmCreate) ; with OleContainer1 do if (State <> osEmpty) then SaveToStream(Stream); Stream.Seek(Sizeof(TStreamHeader), 0); FileStream.CopyFrom(Stream, Stream.Size - SizeOf(TStreamHeader)); Stream.Free; FileStream.Free; end;
OleContainer1 包含的服务器对象是中文 Word 6.0 ,程序中将分离出的数据存储在磁盘文件“ TEST.DOC ”上。如果希望存储在不同的媒介上,可以使用相应的 Stream 对象,分离的方法类似。但是,这种方法并非对所有的 OLE 服务器数据都适用,如 Windows 95 附件中的写字板( WordPad )就不行。 |