Delphi's "Dynamic Form" technology actual application
Xishan flying fox
2003-4-24
Keywords: Delphi, DFM, Form
For me to ignite less, let's take some information first.
== begin =================================
DFM file and standard text file conversion
In the Delphi visualization design environment, the programmer is allowed to browse and modify the DFM file in the code editor. When using the file / open command to open the DFM file directly or select the View as text command on the pop-up menu of the Form Design window, you will appear in the text form in the editor. In some information, this text is called the form design script. This script editing function provided by Delphi is a major supplement to the Delphi visual design. Of course, this script editing capability is limited, thanks that the part cannot be added and deleted in the script, because the code and the DFM script are closely connected, any addition and modification can cause inconsistencies. But in the dynamically generated DFM file, there is no such limit.
In fact, the DFM file content is binary data, and its script is automatically converted through the Delphi development environment, and the Classes library unit in the Delphi VCL provides file DFM in the binary stream and the mutual transformation of its script. They are ObjectBinaryTotext and ObjectTextTobinary, ObjectResourceTEXT, and ObjectTextToreSource.
The ObjectBinaryTotext process converts the components stored in the binary stream to the text-based expression, so that you can use the text handler to process, you can also use the text editor to find and replace the operation, and finally convert the text to a binary stream. part.
Contrary to the ObjectTextTobinary process, reverse the ObjectBinaryTotext, convert the txt file into a binary stream, and as long as the writing of the TXT file content complies with the DFM script syntax, ObjectTextTobinary converts any program generated TXT file to the part, this function is also The dynamic generation and editing of the DFM file laid the foundation.
How to save this form into a text format during the running process?
Zswang (with water) (2001-11-21 9:52:59) 0 points
Function ComponentTostring (Component: Tcomponent): String;
VAR
Binstream: TMemoryStream;
Strstream: Tstringstream;
String;
Begin
Binstream: = TMEMORYSTREAM.CREATE;
Try
Strstream: = TSTRINGSTREAM.CREATE (S);
Try
Binstream.writeComponent (Component);
Binstream.seek; 0, Sofrombeginning;
ObjectBinaryTotext (Binstream, strstream);
Strstream.seek (0, SOFROMBEGINNIN);
Result: = strstream.datastring;
Finally
STRSTREAM.FREE;
END;
Finally
Binstream.freeend;
End; {companyTostring}
Function stringtocomponent (value: string; instance: tcomponent): tComponent
VAR
Strstream: Tstringstream;
Binstream: TMemoryStream;
Begin
Strstream: = TstringStream.create (Value);
Try
Binstream: = TMEMORYSTREAM.CREATE;
Try
ObjectTextTobinary (Strstream, Binstream);
Binstream.seek; 0, Sofrombeginning;
Result: = binstream.readComponent;
Finally
Binstream.free;
END;
Finally
STRSTREAM.FREE;
END;
End; {StringTOComponent}
Reply to: zswang (Particle water) (2001-11-21 9:54:28) 0 points
Procedure TFORM1.BUTTON1CLICK (Sender: TOBJECT);
Begin
Memo1.text: = ComponentToString (Self);
END;
Reply to: zswang (with water) (2001-11-21 9:58:13) 0 points
Procedure TFORM1.BUTTON2CLICK (Sender: TOBJECT);
Begin
Stringtocomponent
'Object Label1: TLABEL' # 13 # 10
'Left = 232' # 13 # 10
'TOP = 56' # 13 # 10
'Width = 26' # 13 # 10
'Height = 13' # 13 # 10
'Capen = # 20320 # 22909' # 13 # 10
'Font.charset = GB2312_CHARSET' # 13 # 10
'Font.color = CLRED' # 13 # 10
'Font.height = -13' # 13 # 10
'Font.name = # 23435 # 20307' # 13 # 10
'Font.style = [] # 13 # 10
'ParentFont = false' # 13 # 10
'end' # 13 # 10, label1);
END;
// To register the class
== End =============================================================
Ok, understand the above words, some friends will naturally think that using these functions should be able to get some useful things, I have made a little application, and I have applied it to the project, now I Come to everyone's full description: First, I ask my program to have the following ability:
1. My program's form can be dynamically replaced, do not compile EXE, just replace a DFM form design script (Of course, you can re-encapsulate this DFM file, such as replacing TXT suffixes, etc.).
2. I can preview all DFM files, let it turn into actual FORM views.
Don't underestimate these two points, in many cases, this significance is very significant, raising several examples 1, can complete the interface design and program design, division of labor 2 on-site maintenance, some interface adjustments and function settings You need to find the source code to Delphi to compile it. The old difference to do the MIS class should be able to experience the benefits from this. 3 Some function interface is simple, just let the user download a DFM file to cover the original Yes.
Ok, I don't have any words, the following details how to reach the above two points.
Obviously we have to turn a text into a form, then use this function:
Function stringtocomponent (value: string; instance: tcomponent): tComponent
VAR
Strstream: Tstringstream;
Binstream: TMemoryStream;
Begin
Strstream: = TstringStream.create (Value);
Try
Binstream: = TMEMORYSTREAM.CREATE;
Try
ObjectTextTobinary (Strstream, Binstream);
Binstream.seek; 0, Sofrombeginning;
Result: = binstream.readComponent;
Finally
Binstream.free;
END;
Finally
STRSTREAM.FREE;
END;
END;
However, all CLASS must be registered, for example, the following form1frm.dfm file
Object Form1: TFORM1
LEFT = 222
TOP = 168
Width = 485
HEIGHT = 290
CAPTION = 'FORM1'
Color = CLBTNFACE
Font.charset = default_charset
Font.color = CLWINDOWTEXT
Font.height = -11
Font.name = 'MS SANS Serif'
Font.style = []
OldcreateOrder = FALSE
Pixelsperinch = 96
TEXTHEIGHT = 13
Object Panel1: TPANEL
LEFT = 0
TOP = 0
Width = 477
HEIGHT = 33
Align = altop
Taborder = 0
Object Bitbtn1: Tbitbtn
LEFT = 4
TOP = 4
Width = 75
HEIGHT = 25
CAPTION = 'OK'
Taborder = 0
end
end
Object Memo1: TMEMO
LEFT = 0
TOP = 33
Width = 477
HEIGHT = 230
Align = alclient
Taborder = 1
end
end
You should use this,
Var List: TstringList; Form: TForm
...
List.Lines.LoadFromfile ('form1frm.dfm');
RegisterClass (TFORM1);
RegisterClass (TPANEL);
RegisterClass (TbitBTN);
RegisterClass (TMEMO);
Form: = StringTOComponent (list.lines.text, nil);
Form.showModal ();
...
This will display a form.
However, this has a problem, and the VCL control of Delphi is fixed. It is not a problem with registerclass (...), but TFORM1 is not, if even TFORM1 is registered, you cannot reach the second point requirements. We can change it, because all forms are inherited from TForm, so you should use the registration TForm to replace, so there is a function below:
Function loadTextform (filename: string): tform;
VAR
List: tstrings;
Firstline: String;
IPOS: Integer;
Form: TForm;
Begin
Result: = NIL;
If FileExists (filename) = false then
EXIT;
Form: = tForm.create (Application);
List: = TSTRINGLIST.CREATE;
Try
List.loadfromfile (filename);
if list.count = 0 THEN
EXIT;
Firstline: = List [0];
IPOS: = POS (':', firstline);
If ipos = 0 THEN / / Can't find ':', format is not
EXIT;
List [0]: = Copy (Firstline, 1, IPOS) 'TFORM';
DeleteErrorLines (List);
StringTOComponent (list.text, form);
Result: = form;
Except
Form.free;
Result: = NIL;
END;
List.free;
END;
The principle is to read the category of the form into TFORM after reading the DFM file. There is also a function:
Procedure deleteErrorLines (List: tstrings);
VAR
i: integer;
Line: String;
Begin
if list.count = 0 THEN
EXIT;
I: = 0;
While i Begin Line: = trim (List [i]); IF Copy (Line, 1, 2) = 'on' Then List.delete (i) Else INC (I); END; END; This function deletes any rows that are "on" starting. It should be in Delphi. All controls are beginning to start with "on", deleting such rows, can guarantee StringTOComponent (List.Text, Form); If you don't make a mistake, you can write a DFM form viewer with the above two functions. So far, I haven't found any DFM form viewer. This way we have completed the second request. In practical applications, a form is almost certain will have an event handler, so we have to reach the first request. I have provided two programs here, each has advantages and disadvantages: Option One: When the programmer is developed, in the form of formcreate (...), generates a form file with loadTextForm (...), then moves all the controls on the form to this form, and finally find the control on the form, dynamic Set the event handler. This method requires a good set of control naming rules, and the development is more cumbersome, enjoy the Delphi's IDE, the benefits of automatically generate event association code. However, the production staff of the FORM file is small, they can use Delphi to make a form directly. Option II: Use this function Procedure readform (AFILOM: TComponent; AfileName: String = '); VAR FRMSTRINGS: TSTRINGS; Begin RegisterClass (TPERSISTENTCLASS (AFROM.CLASSTYPE); FRMSTRINGS: = TSTRINGLIST.CREATE; Try if Trim (AfileName) = '' Then frmstrings.loadfromfile (GSPATHINFO '/' AFROM.NAME '. TXT') Else frMstrings.loadfromfile (afilename); While Afrom.comPonentcount> 0 do afrom.components [0] .destroy; AFROM: = StringTOComponent (frMstrings.text, afrom) Finally FRMSTRINGS.FREE; END; UnregisterClass (TPERSISTENTCLASS (AFROM.CLASSTYPE); END; Call Readform (Self, ...) in FormCreate. This scheme does not limit the limit of the first program, but the developer must first complete a complete Form file to the FORM file producer, the creation of the FORM file cannot modify the NAME of the control, can not add or delete the control, and must be retained Person given all event handlers and cannot modify the function name. However, many problems can write an Form editor to ensure that you can't. The specific code is not written. I think there is also a problem with good programs to solve dynamic forms, I hope everyone will discuss. (The above code is written using Delphi6) Finally, I give a Unit of a function of the dynamic form in my actual project. {************************************************ Module No .: J001DFMFUNC Module Name: DFM Form Function Set Unit Author: Liu Aijun Establishment date: December 2, 2002 Last modified date: Note: This unit contains some functions for file dynamics based on files in Delphi form file format ************************************* ****************} UNIT J001DFMFUNC; Interface Uses Windows, Messages, Sysutils, Variants, Classes, Graphics, Controls, Forms, Dialogs, Extctrls, Dbctrls, Grids, DBGRIDS, Buttons, Stdctrls, Comctrls, Dbcgrids, ButtonComps, Tabs, QRYGLOBAL Type Tallcomponentclass = array of tpersistentclass; Procedure InitclasStype (ClassArray: TallComponentClass); Function ComponentTostring (Component: Tcomponent): String; Function stringtocomponent (value: string; instance: tcomponent): tComponent Procedure RegisterallClasses (aallcmpclass: tallcomponentclass); Procedure UnregisterallClasses (aallcmpclass: tallcomponentclass); Function getObjectString (list: tstrings; begline: integer = 0; typeString: String = ''): string; Function loadTextform (filename: string): tform; Function LoadTextform2 (filename: string; out errmsg: string): TForm; Procedure deleteErrorLines (List: tstrings); Procedure readform (AFILOM: TComponent; AfileName: String = '); Const RegisteredCompoentClassCount = 32; // A number of sets VAR AllCMPClass: TallcomponentClass; // Storage control class IMPLEMentation // Initialization can be parsed, can increase Procedure InitclasStype (ClassArray: TallComponentClass); Begin SetLength (AllCMPClass, RegisteredCompoentClassCount); AllCMPClass [0]: = TFORM; AllCMPClass [1]: = TGROUPBOX; AllCMPClass [2]: = TPANEL; AllCMPClass [3]: = TscrollBox; AllCMPClass [4]: = TLABEL; AllCMPClass [5]: = tbutton; AllCMPClass [6]: = Tbitbtn; AllCMPClass [7]: = tspeedbutton; AllCMPClass [8]: = TSTRINGGRID; AllCMPClass [9]: = TIMAGE; AllCMPClass [10]: = TBevel; AllCMPClass [11]: = TSTATICTEXT; AllCMPClass [12]: = TTABCONTROL; AllCMPClass [13]: = TPAGECONTROL; AllCMPClass [14]: = TTABSHEET AllCMPClass [15]: = TDBNAVIGATOR; AllCMPClass [16]: = TDBTEXT; AllCMPClass [17]: = TDBEDIT; AllCMPCLASS [18]: = TDBMEMO; AllCMPClass [19]: = TDBGRID; AllCMPClass [20]: = TDBCTRLGRID; AllCMPClass [21]: = TMEMO; AllCMPClass [22]: = Tsplitter; AllCMPClass [23]: = Tcheckbox; AllCMPClass [24]: = TEDIT; AllCMPClass [25]: = TListBox; AllCMPClass [26]: = TcomboboX; AllCMPClass [27]: = TDATETIMEPICKER; AllCMPClass [28]: = TIMAGEBUTTON AllCMPClass [29]: = TTABSET; AllCMPClass [30]: = TTREEVIEW; AllCMPClass [31]: = TLISTVIEW; END; Procedure RegisterallClasses (aallcmpclass: tallcomponentclass); VAR i: integer; Begin For i: = 0 to registeredcompoentclasscount-1 do RegisterClass (AallCMPClass [i]); END; Procedure UnregisterallClasses (aallcmpclass: tallcomponentclass); VAR i: integer; Begin For i: = 0 to registeredcompoentclasscount-1 do UnregisterClass (aallcmpclass [i]); END; Function ComponentTostring (Component: Tcomponent): String; VAR Binstream: TMemoryStream; Strstream: Tstringstream; String; Begin Binstream: = TMEMORYSTREAM.CREATE; Try Strstream: = TSTRINGSTREAM.CREATE (S); Try Binstream.writeComponent (Component); Binstream.seek; 0, Sofrombeginning; ObjectBinaryTotext (Binstream, strstream); Strstream.seek (0, SOFROMBEGINNIN); Result: = strstream.datastring; Finally STRSTREAM.FREE; END; Finally Binstream.free END; END; Function stringtocomponent (value: string; instance: tcomponent): tComponent VAR Strstream: Tstringstream; Binstream: TMemoryStream; Begin Strstream: = TstringStream.create (Value); Try Binstream: = TMEMORYSTREAM.CREATE; Try ObjectTextTobinary (Strstream, Binstream); Binstream.seek; 0, Sofrombeginning; Result: = binstream.readComponent; Finally Binstream.free; END; Finally STRSTREAM.FREE; END; END; Function getObjectString (list: tstrings; begline: integer = 0; typeString: String = ''): string; VAR I, Ibegcount, Indcount: Integer; Objstring, Line, ClassStr: String Begin Ibegcount: = 0; IENDCOUNT: = 0; ClassStr: = Trim (Uppercase (TypeString)); For i: = begline to list.count-1 do Begin Line: = Uppercase (List [i]); IF POS ('Object', Line)> 0 THEN Begin IF (TypeString = '') or (POS (':' ClassStr, Line)> 0) THEN IBEGCOUNT; end Else if (ibegcount> ipcount) and (Trim (trim (line) = 'end') THEN INC (IENDCOUNT); IBEGCOUNT> 0 THEN Result: = Result List [i] # 13 # 10; IBEGCOUNT> 0) and (ibegcount = ipcount) THEN EXIT; END; END; Procedure deleteErrorLines (List: tstrings); VAR i: integer; Line: String; Begin if list.count = 0 THEN EXIT; I: = 0; While i Begin Line: = trim (List [i]); IF Copy (Line, 1, 2) = 'on' Then List.delete (i) Else INC (I); END; END; Procedure readform (AFILOM: TComponent; AfileName: String = '); VAR FRMSTRINGS: TSTRINGS; Begin RegisterClass (TPERSISTENTCLASS (AFROM.CLASSTYPE); FRMSTRINGS: = TSTRINGLIST.CREATE; Try if Trim (AfileName) = '' Then frmstrings.loadfromfile (GSPATHINFO '/' AFROM.NAME '. TXT') Else frMstrings.loadfromfile (afilename); While Afrom.comPonentcount> 0 do afrom.components [0] .destroy; AFROM: = StringTOComponent (frMstrings.text, afrom) Finally FRMSTRINGS.FREE; END; UnregisterClass (TPERSISTENTCLASSTY); END; Function loadTextform (filename: string): tform; VAR List: tstrings; Firstline: String; IPOS: Integer; Form: TForm; Begin Result: = NIL; If FileExists (filename) = false then EXIT; Form: = tForm.create (Application); List: = TSTRINGLIST.CREATE; Try List.loadfromfile (filename); if list.count = 0 THEN EXIT; Firstline: = List [0]; IPOS: = POS (':', firstline); If ipos = 0 THEN / / Can't find ':', format is not EXIT; List [0]: = Copy (Firstline, 1, IPOS) 'TFORM'; DeleteErrorLines (List); StringTOComponent (list.text, form); Result: = form; Except Form.free; Result: = NIL; END; List.free; END; Function LoadTextform2 (filename: string; out errmsg: string): TForm; VAR List: tstrings; Firstline: String; IPOS: Integer; Form: TForm; Begin Result: = NIL; If FileExists (filename) = false then Begin Errmsg: = 'invalid file name! " EXIT; END; Form: = tForm.create (Application); List: = TSTRINGLIST.CREATE; Try List.loadfromfile (filename); if list.count = 0 THEN EXIT; Firstline: = List [0]; IPOS: = POS (':', firstline); If ipos = 0 THEN / / Can't find ':', format is not Begin Errmsg: = 'Can't find' ':' ', file format is not right; EXIT; END; List [0]: = Copy (Firstline, 1, IPOS) 'TFORM'; DeleteErrorLines (List); StringTOComponent (list.text, form); Result: = form; Except ON E: Exception DO Begin Form.free; Result: = NIL; Errmsg: = 'Read file error:' E.Message; END; END; List.free; END; INITIALIZATION Begin InitclasStype (AllCMPClass); RegisterallClasses (AllCMPCLASS); END; Finalization UnregisterallClasses (AllCMPClass); END.