My work on the screen form generator began with research into the file structure that defines the look and feel of projects created in the My Visual Database development environment. This time, we will learn the structure of the forms.xml file, which stores all the screen forms of the application, and at the same time we will learn how to read files in XML format.
Unfortunately, the TfxXMLDocument and TfxXMLItem components that are part of the available components for scripts have a number of functionality limitations that make working with them very difficult. In particular, TfxXMLDocument does not load documents in UNICODE encoding, but TfxXMLItem implies the internal representation in this particular encoding. Therefore, after several unsuccessful attempts to use these classes to read the forms.xml file, I decided to turn to OLE technology, in particular, use the MSXML2 offered by Microsoft.
The second serious problem on the way to the goal was the lack of full support for recursive procedure calls in FastScript. It seemed to me that the problem lies in the fact that all variables in FastScript are essentially global, there is no memory stack to store their values. But reading a tree structure implies exactly a recursive call, so I had to break my head over the organization of data storage, which is why the appearance of the recursive procedure came out not as elegant as expected.
Loading the form structure
To load the project’s form structure, use the ReadFormsXML() procedure, which creates an OLE object to work with the XML file, prepares the visual tree display component, and then calls the recursive Tree_AddNode() procedure.
procedure ReadFormsXML(AFileName: string; ATree: TdbTreeView;);
// loading from xml to tree
var
tmpDoc : Variant;
tmpNode: Variant;
tmpRow: integer;
begin
if not FileExists( AFileName ) then
ShowMessage('File not found '+AFileName)
else
begin
tmpDoc := CreateOleObject('MSXML2.DOMDocument'); // create an automation object:
tmpDoc.Load(AFileName); // load file
tmpNode := tmpDoc.DocumentElement; // get first element
ATree.BeginUpdate; // start data updating
try
Tree_Clear(ATree); // clean and prepare the tree
// since MVDB does not support full recursion, we use global variables
ArNode[0] := tmpNode; // put element into array
Level := 0; // adjust level
Tree := ATree; // tree
Tree_AddNode( -1 ); // calling a recursive tree building function
ATree.CollapseAll; // collapse all tree nodes
finally
ATree.EndUpdate; // completion of the tree update
end;
end;
end;
procedure Tree_AddNode( ARow:integer );
// recursive tree loading function
var
tmpItemRow: integer;
tmpPropRow: integer;
tmpRow: integer;
i: integer;
tmpNode: Variant;
tmpCount: integer;
tmpNode2: Variant;
begin
tmpNode := ArNode[ Level ]; // element
if ARow = -1 then
Tree.AddRow // tree root node
else
Tree.AddChildRow( ARow, crLast ); // child tree node
tmpItemRow := Tree.LastAddedRow;
Tree.Cells[0,tmpItemRow ] := tmpNode.NodeName; // name
Tree.Cells[1,tmpItemRow ] := '';
// attibutes
for i := 0 to tmpNode.attributes.Length - 1 do
begin
Tree.AddChildRow( tmpItemRow, crLast );
tmpRow := Tree.LastAddedRow;
Tree.Cells[0,tmpRow ] := tmpNode.attributes.Item(i).Name;
Tree.Cells[1,tmpRow ] := tmpNode.attributes.Item(i).Value;
end;
// child elements
tmpCount := tmpNode.ChildNodes.Length - 1;
for i := 0 to tmpCount do
begin
// go one level deeper
tmpNode := ArNode[ Level ];
ArNode[ Level + 1] := tmpNode.ChildNodes.Item(i);
inc(Level);
Tree_AddNode( tmpItemRow); // recursive call
dec(Level);
end;
end;
Code language: Delphi (delphi)
Loading the table structure
The structure of tables is the basis for solving the problem of automated construction of user interface forms. Since the structure is stored in UNICODE text .ini format, you need to use the TMemIniFile class to read such a file. To display the structure, I also decided to use a tree, in which the top-level elements will be tables, and the branches will be fields. The implementation of the loading algorithm is also made by two procedures: ReadIniFile() opens the file for reading, prepares the tree, and contains the main loop for adding information about tables. Tree_LoadTable() reads and adds information about the fields of a particular table.
procedure ReadIniFile( AFileName: string; ATree: TdbTreeView;);
// reading the database structure from the initialization file
var
tmpRow: integer;
tmpTablesIni : TMemIniFile;
tmpList : TStringList;
tmpItemRow : integer;
i: integer;
begin
if not FileExists(AFileName) then
ShowMessage('File not found '+AFileName)
else
begin
// to work with this file use TMemIniFile - it works fine with Unicode
tmpTablesIni := TMemIniFile.Create(AFileName);
tmpList := TStringList.Create;
try
// read list of tables
tmpTablesIni.ReadSections(tmpList);
ATree.BeginUpdate; // tree update start
Tree_Clear(ATree); // tree preparation
for i := 0 to tmpList.Count - 1 do
begin
ATree.AddRow; // add table information
tmpItemRow := ATree.LastAddedRow;
ATree.Cells[0,tmpItemRow ] := tmpList.Strings[i];
ATree.Cells[1,tmpItemRow ] := '';
// adding information about table fields
Tree_LoadTable( ATree, tmpTablesIni, tmpList.Strings[i], tmpItemRow );
end;
ATree.CollapseAll;
finally
ATree.EndUpdate; // end of tree update
tmpList.Free;
tmpTablesIni.Free;
end;
end;
end;
procedure Tree_LoadTable( ATree: TdbTreeView; ATablesIni : TMemIniFile; ATableName: string; ARow: integer );
// adding information about table fields
var
tmpList : TStringList;
tmpItemRow : integer;
i: integer;
begin
tmpList := TStringList.Create;
try
ATablesIni.ReadSectionValues(ATableName, tmpList);
for i := 0 to tmpList.Count - 1 do
begin
ATree.AddChildRow( ARow, crLast ); // add a child element
tmpItemRow := ATree.LastAddedRow;
ATree.Cells[0,tmpItemRow ] := tmpList.Names[i];
ATree.Cells[1,tmpItemRow ] := tmpList.Values( tmpList.Names[i] );
end;
finally
tmpList.Free;
end;
end;
Code language: Delphi (delphi)
Other procedures
I use the Tree_Clear() procedure to prepare the tree for data loading. It works with an instance of the TdbTreeView class: first, it clears it of rows and columns, and then creates the desired column configuration and sets up the visual representation of the tree.
procedure Tree_Clear(ATree:TdbTreeView);
// tree cleaning and preparation
begin
ATree.ClearRows; // delete lines
ATree.Columns.Clear; // delete columns
// for a hitherto unknown reason it is necessary to escape adding columns
try // add first column
ATree.Columns.Add(TNXTreeColumn);
except
end;
try // add a second column
ATree.Columns.Add(TNxTextColumn);
except
end;
// pseudographic elements
TNXTreeColumn(ATree.Columns[0]).ShowLines := True;
// setting up columns
ATree.Columns[0].Color := clWhite;
ATree.Columns[1].Color := clWhite;
ATree.Columns[0].Header.Caption := 'Element';
ATree.Columns[1].Header.Caption := 'Value';
ATree.Columns[0].width := 300;
ATree.Columns[1].width := 100;
end;
Code language: Delphi (delphi)
To select a project, a button is used, in the click handler of which the main procedures for loading the form structure and table structure are called.
procedure Form1_btnSelectDir_OnClick (Sender: TObject; var Cancel: boolean);
// select a project and read its structure
var
tmpDir: string;
tmpFileName: string;
begin
tmpDir := ExtractFileDir( Application.ExeName);
if SelectDirectory('Select dir', '', tmpDir) then
begin
Form1.edtDir.Text := tmpDir;
ReadIniFile( tmpDir + '\tables.ini', Form1.trvTables);
ReadFormsXML( tmpDir + '\forms.xml', Form1.trvMain);
end;
end;
Code language: JavaScript (javascript)