後一頁
前一頁
回目錄
回首頁
第二十章 開發Delphi物件式數據管理功能(五)

20.3.1.1DFM文件的過程:WriteComponentResFie

   該過程帶有兩個參數FileNameInstanceFileName參數指定要寫入的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函數帶有兩個參數FileNameInstanceFileName參數指定要讀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方法讀出部件。因為函數的Instancevar型式的參數,所以可以存取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和它的腳本之相互轉化的過程。它們是ObjectBinaryToTextObjectTextBinaryObjectResourceToTextObjectTextToResource

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

 

前者是ffInheritedffChildPos置位,後面是都沒置位。

  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腳本語言之間相互轉換的任務由ObjectResourceToTextObjextTextToResource兩個過程完成。

 

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文件讀入記憶體。DelphiStream物件和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是私有變數,用來存檔目前編輯的卡片檔案標簽。DesignWinInit方法實現如下:

 

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;

 

  BackGroundTPanel型式的部件,所有的動態建立物件都插入到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;

 

  因為在DesignWinInit方法中呼叫了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文件,因此用作超媒體系統腳本語言較好的形式。

  ObjectBinaryToTextObjectTextToBinary過程提供了在部件和DFM腳本之間相互轉化的功能,ObjectResourceToTextObjectTextToResoure過程提供了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;

 

  這兩段程式是對整個卡片,即窗體級,進行轉換的。ObjectBinaryToTextObjectTextToBinary過程可以細化到部件級的轉換。因此超媒體腳本語言的編輯可以細化到媒體物件級。

  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 欄位。CardTablePost方法將資料庫的修改反饋到資料庫的物理存儲上。

  上面這段程式是超媒體卡片存儲的部分原始程式,我們就是將卡片存檔在資料庫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物件中提供了LoadFromBitMapSaveToBitMap方法存取點陣圖數據。它們在實現上都是使用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;

 

程式中按兩種方式存取數據,對於點陣圖數據,數據的起點是流的Potition0處,對於圖形或其它型式的Blob數據,則以流的PositionSizeOf(Header) + 1處開始, 即多了個頭資訊。

 

20.3.2.3 BLOB欄位與文本

 

  Delphi BLOB欄位中增加了大型文本的處理能力。可以在TBlobFieldStrings中自由地交換數據。

 

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物件提供了LoadFromStreamSaveToStream方法。

 

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復合文檔嵌入WordDOC數據或RTF數據當然是最佳的選擇。 但問題在於全文檢索系統要求能直接在文檔中搜索關鍵字,因此要求將文檔數據從OLE嵌入數據或文檔中的本地數據中分離出來。

  Delphi 2.0OleContainer部件支援存儲OLE物件數據。OLE物件數據包括兩部分:OLE類描述資訊和OLE伺服器嵌入數據。一般說來,OLE伺服器嵌入數據是以伺服器支援的數據格式存儲的; 比方說,中文Word 6.0的嵌入數據的格式就是Word 6.0文檔的格式。因此,要將文檔數據從OLE 嵌入式文檔中分離出來就是要存取第二部分數據。

我們分析了Delphi 2.0OleContainer物件存取復合文檔的程式,得到分離數據的方法。

  讓我們來看一段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伺服器的嵌入數據。HeaderTStreamHeader記錄型式的變數。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)就不行。


後一頁
前一頁
回目錄
回首頁