menu
  Home  ==>  papers  ==>  colibri_utilities  ==>  component_to_code   

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:

image

and:

  • the UNIT definition is

    type TForm1class(TForm)
                     Panel1TPanel;
                     exit_TButton;
                     clear_TButton;
                     Memo1TMemo;
                     Table1TTable;
                     Table1CustNoTFloatField;
                     Table1CompanyTStringField;
                     Table1Addr1TStringField;
                     DataSource1TDataSource;
                     DBGrid1TDBGrid;
                     procedure FormCreate(SenderTObject);
                     procedure exit_Click(SenderTObject);
                     procedure clear_Click(SenderTObject);
                   private
                   public
                 end// TForm1

  • the .DFM is (partial):

     
    object Form1TForm1
      Left = 225
      Top = 167
      Width = 486
      Height = 251
      // ...ooo...
      object Memo1TMemo
        // ...ooo... 
      end
      object Table1TTable
        Active = True
        DatabaseName = '..\_data'
        TableName = 'customer.db'
        Left = 16
        Top = 40
        object Table1CustNoTFloatField
          FieldName = 'CustNo'
        end
        object Table1CompanyTStringField
          FieldName = 'Company'
          Size = 30
        end
        object Table1Addr1TStringField
          FieldName = 'Addr1'
          Size = 30
        end
      end
      object DataSource1TDataSource
        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

    image

  • here is the tForm1 definition:

    type TForm1class(TForm)
                     Panel1TPanel;
                     exit_TButton;
                     clear_TButton;
                     Memo1TMemo;
                     DataSource1TDataSource;
                     DBGrid1TDBGrid;
                     procedure FormCreate(SenderTObject);
                     procedure exit_Click(SenderTObject);
                     procedure clear_Click(SenderTObject);
                   private
                   public
                     Table1TTable;
                     Table1CustNoTFloatField;
                     Table1CompanyTStringField;
                     Table1Addr1TStringField;
                 end// TForm1

  • with the following .DFM

     
    object Form1TForm1
      Left = 225
      Top = 167
      Width = 486
      Height = 251
      // ...ooo... 
      object Memo1TMemo
        // ...ooo... 
      end
      object DataSource1TDataSource
        Left = 56
        Top = 40
      end

  • and the tForm1.OnCreate event:

    procedure TForm1.FormCreate(SenderTObject);
      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:

    image



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:

image



The generation of the code is the following:

procedure c_dfm_object.generate_object_initialization(
    p_c_result_listtStrings);

  procedure generate_object_initialization_recursive(p_levelInteger;
      p_c_dfm_objectc_dfm_objectp_ownerString);
    var l_indentationString;

    procedure add_line(p_stringString);
      begin
        p_c_result_list.Add(l_indentationp_string);
      end// add_line

    var l_dfm_object_indexInteger;
        l_property_indexInteger;

    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_indexdo
              add_line('  'm_namef_display_name_list
                + ':= '
                + p_c_dfm_object.f_add_tree_value_list(p_levelf_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(SenderTObject);

  procedure build_dfm_tree_recursive(p_levelInteger;
      p_c_dfm_objectc_dfm_object;
      p_c_parent_nodetTreeNode);
    var l_dfm_object_indexInteger;
        l_property_indexInteger;
        l_c_tree_nodetTreeNode;
    begin
      with p_c_dfm_object do
      begin
        l_c_tree_node:= dfm_treeview_.Items.AddChildObject(
            p_c_parent_nodem_namef_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_indexdo
              display(f_spaces(2* p_level+ 2)+ m_namef_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_objectdfm_treeview_ do
    begin
      Items.BeginUpdate;
      build_dfm_tree_recursive(0, g_c_dfm_objectNil);
      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_listtStringListNil;

procedure TForm1.dfm_treeview_Click(SenderTObject);
  var l_c_selected_dfm_objectc_dfm_object;
  begin
    l_c_selected_dfm_object:= c_dfm_object(dfm_treeview_.Selected.Data);

    // -- create if not yet done
    if g_c_result_listNil
      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:
   compile and run
   here is the snapshot of the project:

image

   navigate to the .DFM you want to analyze and click the .DFM. The .DFM MUST be in ASCCI mode. If this is not the case, use our DFM binary to Text utility which does the job

   the parser builds the DFM tree:

image

   click "tree" to fill the tTreeView:
   the tTreeView is displayed

image

   click all the objects you want to create by code. In our case the tTable
   each node is analyzed and the code generated

image

   switch to the "generate_" tab to fetch the code:
   here is the generated code (partial)

image



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
    Name :
    E-mail :
    Comments * :
     

  • 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.
Created: jan-07. Last updated: jul-15 - 98 articles, 131 .ZIP sources, 1012 figures
Copyright © Felix J. Colibri   http://www.felix-colibri.com 2004 - 2015. All rigths reserved
Back:    Home  Papers  Training  Delphi developments  Links  Download
the Pascal Institute

Felix J COLIBRI

+ Home
  + articles_with_sources
    + database
    + web_internet_sockets
    + oop_components
    + uml_design_patterns
    + debug_and_test
    + graphic
    + controls
    + colibri_utilities
      – delphi_net_bdsproj
      – dccil_bat_generator
      – coliget_search_engine
      – dfm_parser
      – dfm_binary_to_text
      – component_to_code
      – exe_dll_pe_explorer
      – dll_process_viewer
      – the_alsacian_notation
      – html_help_viewer
      – cooking_the_code
      – events_record_playback
    + colibri_helpers
    + delphi
    + firemonkey
    + compilers
  + delphi_training
  + delphi_developments
  + sweet_home
  – download_zip_sources
  + links
Contacts
Site Map
– search :

RSS feed  
Blog