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, broken links 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 blog or 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. Starting with Pascal in 1979, he then became involved with Object Oriented Programming, Delphi, Sql, Tcp/Ip, Html, UML. Currently, he is mainly
active in the area of custom software development (new projects, maintenance, audits, BDE migration, Delphi
Xe_n migrations, refactoring), Delphi Consulting and Delph
training. His web site features tutorials, technical papers about programming with full downloadable source code, and the description and calendar of forthcoming Delphi, FireBird, Tcp/IP, Web Services, OOP / UML, Design Patterns, Unit Testing training sessions. |