Ø TReader ดูที่ไฟล์โครงการ Delphi แล้วคุณจะพบกับโค้ดสองสามบรรทัดดังนี้: beginning application.Initialize; Application.CreateForm(TForm1, Form1); Application.Run;end . มาพูดคุยสั้นๆ เกี่ยวกับความหมายของบรรทัดโค้ดเหล่านี้: Application.Initialize ดำเนินการบางอย่างที่จำเป็นสำหรับการเริ่มต้นแอปพลิเคชัน Application.CreateForm(TForm1, Form1) จะสร้างแบบฟอร์มที่จำเป็น และโปรแกรม Application.Run จะเริ่มทำงานและป้อนข้อความ วงจร สิ่งที่เรากังวลมากที่สุดตอนนี้คือประโยคของการสร้างแบบฟอร์ม แบบฟอร์มและส่วนประกอบบนแบบฟอร์มถูกสร้างขึ้นอย่างไร ตามที่กล่าวไว้ก่อนหน้านี้: ส่วนประกอบทั้งหมดในแบบฟอร์ม รวมถึงคุณสมบัติของแบบฟอร์มนั้น จะรวมอยู่ในไฟล์ DFM เมื่อ Delphi คอมไพล์โปรแกรม โปรแกรมจะใช้คำสั่งการคอมไพล์ {$R *.dfm} เพื่อรวบรวมข้อมูลไฟล์ DFM ลงในไฟล์ปฏิบัติการ ดังนั้นสรุปได้ว่าเมื่อสร้างแบบฟอร์มต้องอ่านข้อมูล DFM ต้องใช้อะไรอ่าน แน่นอน TReader! จากการติดตามโปรแกรมทีละขั้นตอน เราจะพบว่าโปรแกรมเรียกใช้เมธอด ReadRootComponent ของ TReader ในระหว่างกระบวนการสร้างแบบฟอร์ม หน้าที่ของเมธอดนี้คือการอ่านส่วนประกอบรูทและส่วนประกอบทั้งหมดที่เมธอดเป็นเจ้าของ มาดูการใช้งานวิธีนี้กันดีกว่า: function TReader.ReadRootComponent(Root: TComponent): TComponent;...begin ReadSignature; Result := nil; GlobalNameSpace.BeginWrite; // Loading from stream adds to name space try try ReadPRefix (แฟล็ก I ); ถ้า Root = nil ให้เริ่ม Result := TComponentClass(FindClass(ReadStr)).Create(nil); Result.Name := ReadStr; end else เริ่มต้นผลลัพธ์ := Root; ReadStr; { ละเว้นชื่อคลาส } ถ้า csDesigning ใน Result.ComponentState แล้ว ReadStr อื่น ๆ เริ่มต้นรวม (Result.FComponentState, csLoading); รวม (Result.FComponentState, csReading); FindUniqueName(ReadStr); สิ้นสุด; TClassFinder.Create(TPersistentClass(Result.ClassType), True); ลอง FLookupRoot := Result; G := GlobalLoaded; ถ้า G <> ไม่มีเลย แล้ว FLoaded := TList.Create; ) < 0 แล้วก็ FLoaded.Add(FRoot); รวม (FRoot.FComponentState, csLoading); รวม (FRoot.FComponentState, csReading); FRoot.ReadState(Self); ยกเว้น (FRoot.FComponentState, csReading); ทำ TComponent (FLoaded [I]) ในที่สุดถ้า G = ไม่มีก็ FLoaded ฟรี; ไม่มี; end; ในที่สุด FFinder.Free; ... ในที่สุด GlobalNameSpace.EndWrite; end; end; ReadRootComponent เรียก ReadSignature เพื่ออ่านแท็กวัตถุ ('TPF0') การตรวจจับแท็กก่อนที่จะโหลดออบเจ็กต์จะช่วยป้องกันการอ่านข้อมูลที่ไม่ถูกต้องหรือล้าสมัยโดยไม่ตั้งใจ ลองดูที่ประโยค ReadPrefix(Flags, I) อีกครั้ง ฟังก์ชั่นของเมธอด ReadPrefix นั้นคล้ายกับของ ReadSignature มาก ยกเว้นว่ามันจะอ่านแฟล็ก (PreFix) ที่ด้านหน้าของส่วนประกอบในสตรีม เมื่อออบเจ็กต์ Write เขียนส่วนประกอบลงในสตรีม มันจะเขียนค่าสองค่าไว้ด้านหน้าส่วนประกอบนั้นหรือไม่ ค่าแรกระบุว่าส่วนประกอบนั้นเป็นแบบฟอร์มที่สืบทอดมาจากแบบฟอร์มบรรพบุรุษหรือไม่ และตำแหน่งในแบบฟอร์มนั้นเป็นแฟล็กสำคัญหรือไม่ ; ค่าที่สองระบุลำดับที่สร้างฟอร์มระดับบน จากนั้น หากพารามิเตอร์ Root เป็นศูนย์ ให้สร้างส่วนประกอบใหม่ด้วยชื่อคลาสที่อ่านโดย ReadStr และอ่านแอตทริบิวต์ Name ของส่วนประกอบจากสตรีม มิฉะนั้น ให้ละเว้นชื่อคลาสและกำหนดความเป็นเอกลักษณ์ของแอตทริบิวต์ Name FRoot.ReadState(Self); นี่เป็นประโยคที่สำคัญมาก เมธอด ReadState อ่านคุณสมบัติของส่วนประกอบรูทและส่วนประกอบที่เป็นเจ้าของ แม้ว่าวิธีการ ReadState นี้เป็นวิธีการของ TComponent แต่การติดตามเพิ่มเติมสามารถเปิดเผยได้ว่าในที่สุดแล้ววิธีการดังกล่าวก็ค้นหาวิธี ReadDataInner ของ TReader การใช้งานวิธีนี้มีดังต่อไปนี้: ขั้นตอน TReader.ReadDataInner(Instance: TComponent);var OldParent, OldOwner: TComponent ;เริ่มต้นในขณะที่ไม่ใช่ EndOfList ทำ ReadProperty(Instance); OldParent := Parent; OldOwner := Parent := Instance.GetChildParent; ลอง Owner := Instance.GetChildOwner; ถ้าไม่ได้รับมอบหมาย (เจ้าของ) := Root; ในขณะที่ไม่ใช่ EndOfList ทำ ReadListEnd; มีรหัสบรรทัดนี้: ในขณะที่ไม่ใช่ EndOfList ทำ ReadProperty(Instance); ใช้เพื่ออ่านคุณสมบัติของคอมโพเนนต์รูท ดังที่กล่าวไว้ก่อนหน้านี้ มีทั้งคุณสมบัติที่เผยแพร่ของคอมโพเนนต์เองและคุณสมบัติที่ไม่ได้เผยแพร่ เช่น ด้านซ้ายและบนของ TTimer สำหรับคุณสมบัติที่แตกต่างกันทั้งสองนี้ ควรมีวิธีการอ่านที่แตกต่างกันสองวิธี เพื่อตรวจสอบแนวคิดนี้ เรามาดูการนำวิธีการ ReadProperty ไปใช้กัน ขั้นตอน TReader.ReadProperty(AInstance: TPersistent);…เริ่มต้น… PropInfo := GetPropInfo(Instance.ClassInfo, FPropName); ถ้า PropInfo <> ไม่มี ดังนั้น ReadPropValue(Instance, PropInfo) จะเริ่มต้น { ไม่สามารถกู้คืนจากข้อผิดพลาดในคุณสมบัติที่กำหนดได้อย่างน่าเชื่อถือ } FCanHandleExcepts := เท็จ; Instance.DefineProperties(Self); FCanHandleExcepts := True; if FPropName <> '' ดังนั้น PropertyError(FPropName); อ่านจากชื่อแอตทริบิวต์ของไฟล์ PropInfo := GetPropInfo(Instance.ClassInfo, FPropName); รหัสนี้เพื่อรับข้อมูลของแอตทริบิวต์ที่เผยแพร่ FPropName ดังที่คุณเห็นจากโค้ดต่อไปนี้ ถ้าข้อมูลแอตทริบิวต์ไม่ว่างเปล่า ค่าแอตทริบิวต์จะถูกอ่านผ่านวิธี ReadPropValue และวิธีการ ReadPropValue จะอ่านค่าแอตทริบิวต์ผ่านฟังก์ชัน RTTI ซึ่งจะไม่ได้อธิบายรายละเอียดไว้ที่นี่ หากข้อมูลแอตทริบิวต์ว่างเปล่า หมายความว่าแอตทริบิวต์ FPropName ไม่ได้ถูกเผยแพร่ และจะต้องอ่านผ่านกลไกอื่น นี่เป็นวิธีการ DefineProperties ที่กล่าวถึงก่อนหน้านี้ ดังนี้: Instance.DefineProperties(Self); วิธีการนี้เรียกวิธีการ DefineProperty ของ TReader: ขั้นตอน TReader.DefineProperty(const Name: string; ReadData: TReaderProc; WriteData: TWriterProc; HasData: Boolean) ;เริ่มต้นถ้า SameText(Name, FPropName) และ Assigned(ReadData) แล้ว start ReadData(Self); FPropName := ''; end; end; ก่อนอื่นจะเปรียบเทียบว่าชื่อแอตทริบิวต์ read เหมือนกับชื่อแอตทริบิวต์ที่กำหนดไว้ล่วงหน้าหรือไม่ หากเหมือนกันและวิธี ReadData ไม่ว่างเปล่า วิธี ReadData ถูกเรียกให้อ่านค่าแอตทริบิวต์ เอาล่ะ คอมโพเนนต์รูทได้ถูกอ่านแล้ว และขั้นตอนต่อไปคือการอ่านคอมโพเนนต์ที่เป็นของคอมโพเนนต์รูท มาดูวิธีการกัน: ขั้นตอน TReader.ReadDataInner(Instance: TComponent); มีโค้ดแบบนี้หลังจากวิธีนี้: ในขณะที่ไม่ใช่ EndOfList ทำ ReadComponent(nil); กลไกการอ่านส่วนประกอบย่อยจะเหมือนกับการอ่านส่วนประกอบรากที่แนะนำข้างต้น ซึ่งเป็นการทะลุผ่านต้นไม้ลึก จนถึงขณะนี้ กลไกการอ่านส่วนประกอบได้ถูกนำมาใช้แล้ว มาดูกลไกการเขียนของส่วนประกอบกัน เมื่อเราเพิ่มส่วนประกอบลงในแบบฟอร์ม คุณสมบัติที่เกี่ยวข้องจะถูกบันทึกไว้ในไฟล์ DFM กระบวนการนี้เสร็จสิ้นโดย TWriter Ø TWriter อ็อบเจ็กต์ TWriter เป็นอ็อบเจ็กต์ Filer ที่สามารถเขียนข้อมูลลงในสตรีมได้ทันที อ็อบเจ็กต์ TWriter สืบทอดโดยตรงจาก TFiler นอกเหนือจากการแทนที่วิธีการที่สืบทอดมาจาก TFiler แล้ว ยังเพิ่มวิธีการจำนวนมากสำหรับการเขียนประเภทข้อมูลต่างๆ (เช่น Integer, String, Component เป็นต้น) ออบเจ็กต์ TWriter มีวิธีการมากมายสำหรับการเขียนข้อมูลประเภทต่างๆ ลงในสตรีม ออบเจ็กต์ TWrite จะเขียนข้อมูลลงในสตรีมในรูปแบบที่แตกต่างกันโดยขึ้นอยู่กับข้อมูลที่ต่างกัน ดังนั้นเพื่อให้เชี่ยวชาญการใช้งานและวิธีการใช้งานของออบเจ็กต์ TWriter คุณต้องเข้าใจรูปแบบที่ออบเจ็กต์ Writer เก็บข้อมูล สิ่งแรกที่ควรทราบก็คือสตรีมของออบเจ็กต์ Filer แต่ละรายการมีแท็กออบเจ็กต์ Filer แท็กนี้กินพื้นที่สี่ไบต์และค่าของมันคือ "TPF0" วัตถุ Filer เข้าถึงแท็กสำหรับเมธอด WriteSignature และ ReadSignature ป้ายนี้ส่วนใหญ่จะใช้เพื่อแนะนำการดำเนินการอ่านเมื่อวัตถุ Reader อ่านข้อมูล (ส่วนประกอบ ฯลฯ) ประการที่สอง ออบเจ็กต์ Writer ต้องเว้นแฟล็กไบต์ไว้ก่อนที่จะจัดเก็บข้อมูล เพื่อระบุประเภทข้อมูลที่จะจัดเก็บในภายหลัง ไบต์นี้เป็นค่าประเภท TValueType TValueType เป็นประเภทการแจงนับที่ใช้พื้นที่หนึ่งไบต์และถูกกำหนดดังนี้: TValueType = (VaNull, VaList, VaInt8, VaInt16, VaInt32, VaEnthend, VaString, VaIdent, VaFalse, VaTrue, VaBinary, VaSet, VaLString, VaNil, VaCollection); ดังนั้นสำหรับวิธีการเขียนข้อมูลทุกวิธีของออบเจ็กต์ Writer ในแง่ของการใช้งาน แฟล็กบิตจะต้องถูกเขียนก่อน จากนั้นจึงเขียนข้อมูลที่เกี่ยวข้อง และทุกวิธีการอ่านข้อมูลของออบเจ็กต์ Reader จะต้องอ่านบิตแฟล็กก่อนเพื่อการตัดสิน ตรงกับข้อมูลที่อ่าน มิฉะนั้นจะมีการสร้างเหตุการณ์ข้อยกเว้นที่ระบุว่าข้อมูลที่อ่านไม่ถูกต้อง ธง VaList มีวัตถุประสงค์พิเศษ ใช้เพื่อระบุว่าจะมีชุดของรายการประเภทเดียวกันในภายหลัง และธงที่ระบุจุดสิ้นสุดของรายการต่อเนื่องกันคือ VaNull ดังนั้น เมื่ออ็อบเจ็กต์ Writer เขียนรายการที่เหมือนกันหลายรายการติดต่อกัน ขั้นแรกจะเขียนแฟล็ก VaList ด้วย WriteListBegin จากนั้นจึงเขียนแฟล็ก VaNull หลังจากเขียนรายการข้อมูล เมื่ออ่านข้อมูลเหล่านี้ จะเริ่มต้นด้วย ReadListBegin ลงท้ายด้วย ReadListEnd และใช้ ฟังก์ชัน EndofList อยู่ตรงกลาง ตรวจสอบว่ามีแฟล็ก VaNull หรือไม่ มาดูวิธีการที่สำคัญมากของ TWriter: WriteData: Procedure TWriter.WriteData(Instance: TComponent);... beginning... WritePrefix(Flags, FChildPos); ถ้า UseQualifiedNames แล้ว WriteStr(GetTypeData(PTypeInfo(Instance.ClassType .ClassInfo)) .UnitName + '.' + instance.ClassName) อย่างอื่น WriteStr(Instance.ClassName); WriteStr(Instance.Name); PropertiesPosition := ตำแหน่ง; if (FAncestorList <> nil) และ (FAncestorPos < FAncestorList.Count) จากนั้นเริ่มต้นถ้า Ancestor <> nil จากนั้น Inc(FancestorPos); Inc(FChildPos); ); WriteListEnd; ...end; จากเมธอด WriteData เราจะเห็นภาพรวมของการสร้างข้อมูลไฟล์ DFM ขั้นแรกให้เขียนแฟล็ก (PreFix) หน้าคอมโพเนนต์ จากนั้นเขียนชื่อคลาสและชื่ออินสแตนซ์ ปฏิบัติตามคำสั่งนี้ทันที: WriteProperties(Instance); ใช้เพื่อเขียนคุณสมบัติของส่วนประกอบ ตามที่กล่าวไว้ก่อนหน้านี้ ในไฟล์ DFM มีทั้งแอตทริบิวต์ที่เผยแพร่และแอตทริบิวต์ที่ไม่ได้เผยแพร่ วิธีการเขียนของแอตทริบิวต์ทั้งสองนี้ควรจะแตกต่างกัน มาดูการใช้งาน WriteProperties: Procedure TWriter.WriteProperties(Instance: TPersistent);...begin Count := GetTypeData(Instance.ClassInfo)^.PropCount; ถ้า Count > 0 ให้เริ่ม GetMem(PropList, Count * SizeOf(Pointer )); ลอง GetPropInfos(Instance.ClassInfo, PropList); for I := 0 to Count - 1 do beginning PropInfo := PropList^[I]; ถ้า PropInfo = nil แล้ว Break; ถ้า IsStoredProp (Instance, PropInfo) แล้ว WriteProperty (Instance, PropInfo); สิ้นสุด; Instance.DefineProperties(Self);end โปรดดูโค้ดต่อไปนี้: ถ้า IsStoredProp(Instance, PropInfo) จากนั้น WriteProperty(Instance, PropInfo) ฟังก์ชัน IsStoredProp จะใช้ตัวระบุพื้นที่เก็บข้อมูลเพื่อพิจารณาว่าจำเป็นต้องบันทึกคุณสมบัตินั้นหรือไม่ ให้เรียก WriteProperty เพื่อบันทึกคุณสมบัติ และ WriteProperty จะถูกนำไปใช้ผ่านชุดของ RTTI ฟังก์ชั่น หลังจากบันทึกคุณสมบัติที่เผยแพร่แล้ว ต้องบันทึกคุณสมบัติที่ไม่ได้เผยแพร่ ซึ่งทำได้ผ่านโค้ดนี้: Instance.DefineProperties(Self); มีการกล่าวถึงการใช้งาน DefineProperties ก่อนหน้านี้ . เอาล่ะ จนถึงขณะนี้ยังคงมีคำถาม: ส่วนประกอบย่อยที่เป็นเจ้าของโดยส่วนประกอบรูทได้รับการบันทึกอย่างไร ลองดูที่วิธีการ WriteData (วิธีนี้ถูกกล่าวถึงก่อนหน้านี้): ขั้นตอน TWriter.WriteData(Instance: TComponent);...begin... ถ้าไม่ใช่ IgnoreChildren ให้ลอง if (FAncestor <> nil) และ (FAncestor คือ TComponent) จากนั้น เริ่มต้นถ้า (FAncestor คือ TComponent) และ (csInline ใน TComponent(FAncestor).ComponentState) จากนั้น FRootAncestor := TComponent(Fancestor); FancestorList := TList.Create; TComponent(Fancestor).GetChildren(AddAncestor, FRootAncestor); สิ้นสุด ถ้า csInline ใน Instance.ComponentState; ฟรี; สิ้นสุด; สิ้นสุด; คุณลักษณะ IgnoreChildren อนุญาตให้วัตถุ Writer เก็บส่วนประกอบโดยไม่ต้องจัดเก็บส่วนประกอบย่อยที่เป็นของส่วนประกอบ ถ้าคุณสมบัติ IgnoreChildren เป็น True วัตถุ Writer จะจัดเก็บส่วนประกอบโดยไม่ต้องจัดเก็บส่วนประกอบลูกที่เป็นเจ้าของ มิฉะนั้นจะต้องเก็บส่วนประกอบย่อยไว้ Instance.GetChildren(WriteComponent, FRoot); นี่เป็นประโยคที่สำคัญที่สุดเมื่อเขียนองค์ประกอบย่อย โดยจะใช้เมธอด WriteComponent เป็นฟังก์ชันเรียกกลับและเป็นไปตามหลักการของการสำรวจเชิงลึกครั้งแรกของแผนผัง หากองค์ประกอบรูท FRoot มีองค์ประกอบย่อย ให้ใช้ WriteComponent เพื่อบันทึกองค์ประกอบย่อย ด้วยวิธีนี้ สิ่งที่เราเห็นในไฟล์ DFM จึงเป็นโครงสร้างส่วนประกอบที่มีลักษณะคล้ายต้นไม้