後一頁 前一頁 回目錄 回首頁 |
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; beginButtonPos := 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: LonginReadInteger 方法從流中讀取整型數字。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 thenfor 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文件方法、步驟和注意事項。 |
後一頁 前一頁 回目錄 回首頁 |