|
Component to Code - Felix John COLIBRI.
|
- abstract : generate the component creation and initialization code by
analyzing the .DFM. Handy to avoid installing components on the Palette
when examining new libraries
- key words : component - CONSTRUCTOR - .DFM scanner - .DFM parser - .DFM
object tree - tTreeView - code generation
- software used : Windows XP, Delphi 6
- hardware used : Pentium 2.800Mhz, 512 M memory, 140 G hard disc
- scope : Delphi 1 to 2006, Turbo Delphi for Windows, Kylix
- level : Delphi developer
- plan :
1 - Load from .DFM or create by code
When you spelunk in olden libraries, it sometimes happen that the demos require
the installation of some components on the Palette. This is not very complex,
but handling version differences, and using different Delphi versions would
require many installation. In addition, opening the tForms without the
components on the Palette will sometimes trigger a flood of warning about all
those unknown component, and trying to quit brings back another avalanche of
dialogs asking you whether you want to remove the unknown components from the
tForm.
Instead of letting the Delphi streaming system create the component and
inititialize them, we can create the component by code, in the tForm.OnCreate
event usually, avoiding the Palette installation.
This simple utility simply analyzes the .DFM and then generates the creation
code
2 - Generate Component Creation
2.1 - quick example
Here is a example with a tDataSet. This example is fake, since, for the
tDataSet, we ALWAYS use the Palette / Object Inspector. But the example
will let you understand the objective and benefit or this tool
So here is a simple tForm, with a "CUSTOMERS.DB" Table from the Mastapp
DBDEMO database:
and:
- the UNIT definition is
type TForm1= class(TForm)
Panel1: TPanel;
exit_: TButton;
clear_: TButton;
Memo1: TMemo;
Table1: TTable;
Table1CustNo: TFloatField;
Table1Company: TStringField;
Table1Addr1: TStringField;
DataSource1: TDataSource;
DBGrid1: TDBGrid;
procedure FormCreate(Sender: TObject);
procedure exit_Click(Sender: TObject);
procedure clear_Click(Sender: TObject);
private
public
end; // TForm1
|
- the .DFM is (partial):
object Form1: TForm1
Left = 225
Top = 167
Width = 486
Height = 251
// ...ooo...
object Memo1: TMemo
// ...ooo...
end
object Table1: TTable
Active = True
DatabaseName = '..\_data'
TableName = 'customer.db'
Left = 16
Top = 40
object Table1CustNo: TFloatField
FieldName = 'CustNo'
end
object Table1Company: TStringField
FieldName = 'Company'
Size = 30
end
object Table1Addr1: TStringField
FieldName = 'Addr1'
Size = 30
end
end
object DataSource1: TDataSource
DataSet = Table1
Left = 56
Top = 40
end
end
|
- when we run the program, Delphi creates the Table1 component and all the
persistent fields
IF the tTable was not on the Palette, we could achieve the same result by
creating the table and the fields by code:
- while in design, we remove the Table1 from the tForm
- here is the tForm1 definition:
type TForm1= class(TForm)
Panel1: TPanel;
exit_: TButton;
clear_: TButton;
Memo1: TMemo;
DataSource1: TDataSource;
DBGrid1: TDBGrid;
procedure FormCreate(Sender: TObject);
procedure exit_Click(Sender: TObject);
procedure clear_Click(Sender: TObject);
private
public
Table1: TTable;
Table1CustNo: TFloatField;
Table1Company: TStringField;
Table1Addr1: TStringField;
end; // TForm1
|
- with the following .DFM
object Form1: TForm1
Left = 225
Top = 167
Width = 486
Height = 251
// ...ooo...
object Memo1: TMemo
// ...ooo...
end
object DataSource1: TDataSource
Left = 56
Top = 40
end
|
- and the tForm1.OnCreate event:
procedure TForm1.FormCreate(Sender: TObject);
begin
Table1:= TTable.Create(Self);
With Table1 do
begin
// -- manually removed : Active:= True;
DatabaseName:= '..\_data';
TableName:= 'customer.db';
Table1CustNo:= TFloatField.Create(Table1);
With Table1CustNo do
begin
FieldName:= 'CustNo';
end; // With Table1CustNo
Table1Company:= TStringField.Create(Table1);
With Table1Company do
begin
FieldName:= 'Company';
Size:= 30;
end; // With Table1Company
Table1Addr1:= TStringField.Create(Table1);
With Table1Addr1 do
begin
FieldName:= 'Addr1';
Size:= 30;
end; // With Table1Addr1
end; // With Table1
// -- manually added
DataSource1.DataSet:= Table1;
Table1.Open;
end; // FormCreate
|
- and when we run the program, we get the same result:

2.2 - The Component_to_Code utility
Our component_to_code utility
- parses the .DFM
- builds a tTreeView of all objects
- and when the user clicks on an object, generates the creation code for the
object and its owned components.
This utility borrows heavily from the DFM Parser, which
scans, parses the .DFM and builds a tree like structure. All we had to do was
to add
- the generation of the code from a c_dfm_object CLASS
- the tTreeView which displays the .DFM tree and generates the initialization
whenever the user clicks a node
The UML class diagram of our tree structure with the new method added is now:
The generation of the code is the following:
procedure c_dfm_object.generate_object_initialization(
p_c_result_list: tStrings);
procedure generate_object_initialization_recursive(p_level: Integer;
p_c_dfm_object: c_dfm_object; p_owner: String);
var l_indentation: String;
procedure add_line(p_string: String);
begin
p_c_result_list.Add(l_indentation+ p_string);
end; // add_line
var l_dfm_object_index: Integer;
l_property_index: Integer;
begin // generate_object_initialization_recursive
with p_c_dfm_object do
begin
l_indentation:= f_spaces(2* p_level);
add_line('');
add_line(m_object_name+ ':= '+ m_name+ '.Create('+ p_owner+ ');');
add_line('With '+ m_object_name+ ' do');
add_line('begin');
with m_c_dfm_property_list do
for l_property_index:= 0 to f_dfm_property_count- 1 do
with f_c_dfm_property(l_property_index) do
add_line(' '+ m_name+ f_display_name_list
+ ':= '
+ p_c_dfm_object.f_add_tree_value_list(p_level, f_c_self)+ ';');
with m_c_dfm_object_list_ do
for l_dfm_object_index:= 0 to f_dfm_object_count- 1 do
generate_object_initialization_recursive(p_level+ 1,
f_c_dfm_object(l_dfm_object_index), p_c_dfm_object.m_object_name);
add_line('end; // With '+ m_object_name);
end; // with p_c_dfm_object
end; // generate_object_initialization_recursive
begin // generate_object_initialization
generate_object_initialization_recursive(4, Self, 'Self');
end; // generate_object_initialization
|
The code which builds the tTreeView is:
procedure TForm1.tree__Click(Sender: TObject);
procedure build_dfm_tree_recursive(p_level: Integer;
p_c_dfm_object: c_dfm_object;
p_c_parent_node: tTreeNode);
var l_dfm_object_index: Integer;
l_property_index: Integer;
l_c_tree_node: tTreeNode;
begin
with p_c_dfm_object do
begin
l_c_tree_node:= dfm_treeview_.Items.AddChildObject(
p_c_parent_node, m_name, f_c_self);
with m_c_dfm_property_list do
for l_property_index:= 0 to f_dfm_property_count- 1 do
with f_c_dfm_property(l_property_index) do
display(f_spaces(2* p_level+ 2)+ m_name+ f_display_name_list
+ '= '+ f_display_tree_value_list);
with m_c_dfm_object_list_ do
for l_dfm_object_index:= 0 to f_dfm_object_count- 1 do
build_dfm_tree_recursive(p_level+ 1,
f_c_dfm_object(l_dfm_object_index),
l_c_tree_node);
end; // with p_c_dfm_object
end; // build_dfm_tree_recursive
begin // tree__Click
with g_c_dfm_object, dfm_treeview_ do
begin
Items.BeginUpdate;
build_dfm_tree_recursive(0, g_c_dfm_object, Nil);
FullExpand;
Items.EndUpdate;
end; // with g_c_dfm_object, dfm_treeview_
end; // tree__Click
|
And clicking on a tTreeNode generates the object creation code:
var g_c_result_list: tStringList= Nil;
procedure TForm1.dfm_treeview_Click(Sender: TObject);
var l_c_selected_dfm_object: c_dfm_object;
begin
l_c_selected_dfm_object:= c_dfm_object(dfm_treeview_.Selected.Data);
// -- create if not yet done
if g_c_result_list= Nil
then g_c_result_list:= tStringList.Create;
// -- generate and append to the result
l_c_selected_dfm_object.generate_object_initialization(g_c_result_list);
end; // dfm_treeview_Click
|
2.3 - Mini User Manual
Here is how to use this utility:
To finish the job
- save, or paste the generated code to the clipboard
- remove the generated parts from the .DFM
- in the .PAS, in the tForm1 definition, shift the declarations from the
PUBLISHED section to the PUBLIC section
- insert the generated code in a method (usually tForm1.OnCreate)
- eventually do some housecleaning. In our case
- remove the Active:= True from the Table1 code
- add the DataSource link, and open the Table
In other cases, we had to rearrange the creation order since some components
reference other components which might be created later by our code. This
could be avoided by carefully selecting the creation order in the
tTreeView, but quite often we had no idea of the links between the objects
before seeing the code (or getting some strange results since the linking
points to NIL objects created later)
3 - Improvements
3.1 - Hacking or not Hacking ?
I am fully aware that this a somehow "anti delphi spirit" technique. You are
supposed to put everything on the Palette and use the Object Inspector to
set the initial values.
Well, since the Apple ][, I am more used to type and code rather than drag and
click. And when this proves to be more efficient (in terms of hours, or
dollars), there is nothing which will stop me.
3.2 - Finishing the job
We could easily add other features to the tool
- create ALL object creation, and let the user cut and paste in the Pascal
code
- add the .DFM removal, tForm definition shift, add insertion of the code
into the .PAS
- and while we are at it, perform some topological sort on the creation to
make sure the creation order is correct
At this point, we believe that we reached the balance between what was gained
and what it would cost to add those possibilities, so we stopped. But if
someone is willing to pay for the development, we're ready !
More interestingly, instead of parsing the .DFM, we could have directly
streamed the .DFM using the Delphi routines. Since we already had the parsing
routines, and we had only a couple of hours budget, we chose this simple
parsing route, but the other path will definitely explored in a later utility.
4 - Download the Delphi Sources
Here are the source code files:
The .ZIP file(s) contain:
- the main program (.DPR, .DOF, .RES), the main form (.PAS, .DFM), and any
other auxiliary form
- any .TXT for parameters, samples, test data
- all units (.PAS) for units
Those .ZIP
- are self-contained: you will not need any other product (unless expressly
mentioned).
- for Delphi 6 projects, can be used from any folder (the pathes are RELATIVE)
- will not modify your PC in any way beyond the path where you placed the .ZIP
(no registry changes, no path creation etc).
To use the .ZIP:
- create or select any folder of your choice
- unzip the downloaded file
- using Delphi, compile and execute
To remove the .ZIP simply delete the folder.
The Pascal code uses the Alsacian notation, which prefixes identifier by
program area: K_onstant, T_ype, G_lobal, L_ocal, P_arametre,
F_unction, C_lass etc. This notation is presented in the
Alsacian
Notation paper.
As usual:
- please tell us at fcolibri@felix-colibri.com if you found some errors, mistakes, bugs or had
some problem downloading the file. Resulting corrections will be helpful
for other readers
- we welcome any comment, criticism, enhancement, other sources or reference
suggestion. Just send an e-mail to fcolibri@felix-colibri.com.
- or more simply, enter your (anonymous or with your e-mail if you want an
answer) comments below and clic the "send" button
- and if you liked this article, talk about this site to your fellow
developpers, add a link to your links page ou mention our articles in
your newsgroup posts when relevant. That's the way we operate: the more
traffic and Google references we get, the more articles we will write.
5 - The author
Felix John COLIBRI works at the Pascal
Institute. He programs in Pascal since 1979, and is mainly active in the area
of custom software
development and training, and is a frequent speaker at Borland
Developer Conferences. His web site features
tutorials, technical papers about programming with full downloadable source
code, and the description and calendar of forthcoming Delphi,
Interbase, Asp.Net, Ado.Net and OOP / UML training sessions.
|