menu
  Home  ==>  papers  ==>  firemonkey  ==>  simple_firemonkey_object_inspector   

Simple FireMonkey Object Inspector - Felix John COLIBRI.

  • abstract : building a FireMonkey Object Inspector which presents the components of the Form and displays their property names an values and allows the user to modify them at runtime. Comments about our first FireMonkey project.
  • key words : FireMonkey - tFmxObject - Children - RemoveObject - Rtti - tRectangle - tText - FireMonkey controls Class Diagram
  • software used : Windows XP Home, Delphi XE2, update 1
  • hardware used : Pentium 2.800Mhz, 512 M memory, 140 G hard disc
  • scope : Delphi XE2 FireMonkey
  • level : Delphi developer
  • plan :


1 - Simple FireMonkey Object Inspector

This Delphi XE2 FireMonkey project displays the property names and property values in a VERY SIMPLE Object Inspector, allowing us, at runtime, to display the Form's component properties and change their values.

This simplified "Object Inspector" only displays the properties (not the events), and its presentation is quite primitive.




2 - Reading and Writing Properties

2.1 - Object Inspector

The standard way to present an Object Inspector is to use
  • a drawing surface with the Property / Events tabs, each with 2 columns, one for the name, the other for the value
  • an in-place edit for updating the property / event values
The mouse is then monitored to let us
  • select a property / event
  • change the size of the name or value column


2.2 - FireMonkey Name / Value pair display

The first step is to be able to display some Name / Value pair on the screen. The simplest way is to dynamically create tLabel / tEdit pairs.

We originally wanted to use a tLabel for the value and replace it with the in-place edit, but the tLabel does not react to the clic. So we decided to replace it with a tText, wich does react. But we cannot easily display the surface: the tText.Fill.Color changes the font color, not the background. So we decided to use tRectangles and place the tText on the tRectangle. So the organization is the following:

object_inspector_sketch



The coding is then simple :
   create a FireMonkey HD application
   drop a tPanel on Form1
   type the code which adds our tLabel / tRectangle / tText elements to the tPanel:

Const k_property_value_x= 70+ 2;
      k_value_width= 100;
Var g_ySingle= 5;

Procedure add_property(p_property_namep_property_valueString);
  Var l_c_property_name_labeltLabel;
      l_c_value_rectangletRectangle;
      l_c_property_value_texttText;
      l_heightSingle;
  Begin
    With Form1 Do
    Begin
      l_height:= property_in_place_edit_.Height+ 2;

      l_c_property_name_label:= tLabel.Create(Form1);
      With l_c_property_name_label Do
      Begin
        Parent:= property_panel_;
        Position.X:= 5;
        Position.Y:= g_y+ 2;
        Text:= p_property_name;
      End// with l_c_property_name_label

      l_c_value_rectangle:= tRectangle.Create(Form1);
      With l_c_value_rectangle Do
      Begin
        Parent:= property_panel_;
        Position.X:= k_property_value_x;
        Position.Y:= g_y;
        Width:= k_value_width;
        Stroke.Color:= claRed;
        Height:= l_height;
      End// with l_c_value_rectangle

      l_c_property_value_text:= tText.Create(Form1);
      With l_c_property_value_text Do
      Begin
        Parent:= l_c_value_rectangle;
        Position.X:= 4;
        Position.Y:= 1;
        Width:= k_value_width;
        Height:= l_height- 2;
        HorzTextAlign:= TTextAlign.taLeading;
        Text:= p_property_value;

        // -- the font color
        Fill.Color:= claDarkTurquoise;

        OnMouseDown:= handle_property_value_mousedown;
      End// with l_c_property_value_text
    End// with Form1

    g_y:= g_yl_height+ 2;
  End// add_property

The handle_property_value_mousedown is in charge to display the in-place tEdit. This procedure is defined in the Private part of Form1, and the implementation is:

Procedure tForm1.handle_property_value_mousedown(SenderTObjectButtonTMouseButton;
    ShiftTShiftStateXYSingle);
  Var l_c_sender_texttText;
      l_c_property_value_rectangletRectangle;
  Begin
    l_c_sender_text:= Sender As tText;
    l_c_property_value_rectangle:= l_c_sender_text.Parent As tRectangle;

    With Form1property_in_place_edit_ Do
    Begin
      Parent:= l_c_property_value_rectangle;
      // -- position relative to the PARENT
      Position.X:= 4;
      Position.Y:= 1;

      // -- attach the tText to the in place edit Tag
      Tag:= NativeInt(l_c_sender_text);

      Text:= l_c_sender_text.Text;
      Visible:= True;
    End// with Form1, property_in_place_edit_
  End// handle_property_value_mousedown

where we save in the tEdit.Tag a reference to the tText, to be able to update the tText when the user enters a value on the tEdit

   drop two tEdits for entering example values of the Name and Value of some property, and a tButton to dynamically add the Name / Value pairs on the tPanel:

Procedure TForm1.add_name_value_Click(SenderTObject);
  Begin
    add_property(property_name_edit_.Textproperty_value_edit_.Text);
  End// add_name_value_Click

   compile, run and enter "Height", "77" and click "add_name_value_", and any other example
   here is the display

add_object_inspector_trial

   click on "888"
   the in place tEdit is positioned, made visible and the value from the tText is transfered into the tEdit :

add_object_inspector_trial



To transfer the value types in the in-place tEdit, we create a tEdit and use the tEdit.OnChange, or tEdit.OnKeyDown, like this:

Procedure TForm1.property_in_place_edit_KeyDown(SenderTObjectVar KeyWord;
    Var KeyCharCharShiftTShiftState);
  Var l_c_property_texttText;
  Begin
    If Key= 13
      Then Begin
          property_in_place_edit_.Visible:= False;
          l_c_property_text:= tText(property_in_place_edit_.Tag);
          l_c_property_text.Text:= property_in_place_edit_.Text;
        End;
  End// property_in_place_edit_KeyDown

where we use the tEdit.Tag initialized when the tEdit was positioned to reach the tText control and set the value entered by the user.



Please note

  • we used the new NativeInt integer type to cast the Tag property
  • to set the tText alignment property, we used the QUALIFIED enumerated notation, as required by FireMonkey:

    my_ttext.HorzTextAlign:= TTextAlign.taLeading;



2.3 - Reading and Writing a Control properties

To simulate an Object Inspector, we have to fill a "Selector", which is a combobox containing all component names. Here is the procedure used to fill the combobox:

Procedure fill_property_selector_combobox;
  Var l_component_indexInteger;
  Begin
    With Form1 Do
    Begin
      property_selector_combobox_.Items.Clear;
      For l_component_index:= 0 To ComponentCount- 1 Do
        property_selector_combobox_.Items.Add(Components[l_component_index].Name);
      property_selector_combobox_.ItemIndex:= 0;
    End// with Form1
  End// fill_property_selector_combobox



Once a name is selected, we have to fill the property names and values. This can be done

  • by locating the component from its name
  • by using RTTI to extract the component property names and values
Procedure fill_object_inspector(p_component_nameString);
  Var l_c_componenttComponent;

      l_pt_property_listPPropList;
      l_property_count : Integer;
      l_property_index : Integer;
      l_property_namel_property_valueString;
  Begin
    l_c_component:= Form1.FindComponent(p_component_name);
    If l_c_component Is tFmxObject
      Then Begin
          GetMem(l_pt_property_listSizeOf(Pointer)* GetTypeData(l_c_component.ClassInfo)^.PropCount);

          l_property_count := GetPropList(l_c_component.ClassInfotkProperties
              l_pt_property_listTrue);
          For l_property_index := 0 To l_property_count- 1 Do
          Begin
            l_property_name:= l_pt_property_list^[l_property_index].Name;
            l_property_value:= GetPropValue(l_c_componentl_property_nameTrue);

            add_property(l_property_namel_property_value);
          End;

          FreeMem(l_pt_property_list);
        End;
  End// fill_object_inspector



And before filling the property names and values, we must clear any previous content:

Procedure clear_object_inspector;
  Var l_child_indexInteger;
      l_c_fmx_objecttFmxObject;
  Begin
    With Form1.property_panel_ Do
    Begin
      l_child_index:= ChildrenCount- 1;
      While l_child_index>= 0 Do
      Begin
        l_c_fmx_object:= Children[l_child_index];

        If (l_c_fmx_object Is tLabel)
            Or (l_c_fmx_object Is tRectangle)
          Then Begin
              RemoveObject(l_c_fmx_object);
              l_c_fmx_object.Free
            End;

        Dec(l_child_index);
      End// while l_child_index
    End;
  End// clear_object_inspector

Note that

  • to extract a component's property names and values, we could also use the new Delphi 2010 RTTI unit:

    Procedure fill_object_inspector(p_component_nameString);
      Var l_c_componenttComponent;

          l_rtti_contexttRttiContext;
          l_rtti_typetRttiType;
          l_c_rtti_propertytRttiProperty;
          l_property_namel_property_valueString;
      Begin
        l_c_component:= Form1.FindComponent(p_component_name);
        If l_c_component Is tFmxObject
          Then Begin
              l_rtti_type:= l_rtti_context.GetType(p_c_component.ClassType);
              For l_c_rtti_property In l_rtti_type.GetProperties Do
              Begin
                l_property_name:= l_c_rtti_property.Name;
                If l_c_rtti_property.PropertyType.TypeKind In
                    [tkIntegertkChar
                     , tkFloat
                     , tkString
                     , tkWChartkLStringtkWString
                     , tkEnumeration
                     ]
                  Then l_property_value:= 
                      l_c_rtti_property.GetValue(p_c_component).ToString
                  Else l_property_value:= '?';

                add_property(l_property_namel_property_value);
              End;
            End;
      End// fill_object_inspector

    However the items are not sorted by property name

  • removing from the end of the list simply saves the forward shifting of the pointers if we remove from the start
  • we must also avoid to remove the tLayout Style rectangle, which is the first child of the property_panel_. If we remove it, the border of the display_panel_ vanishes. The
  • if a property value has been selected, the tEdit became a child of the tRectangle containing the value. Therefore, before freeing all the values, we must remove the in place edit from any tRectangle's children list (the in-place tEdit cannot have a Parent which has been freeed). This can be done by the following code :

    Procedure detach_in_place_edit;
        // -- The tEdit becomes invisible
      Begin
        With Form1.property_in_place_edit_ Do
        Begin
          Parent:= Form1;
          Tag:= 0;
          Visible:= False;
        End// with Form1.property_in_place_edit_
      End// detach_in_place_edit

    If we do not reassign the Parent, the children are destroyed when their Parent is destroyed. This was a surprise for us, since we believed that Parent only managed the visibility / clipping, and the destruction was managed ONLY by the Owner. This is not the case in FireMonkey, and not in the VCL.



2.4 - The Final version

Finally, we include the tPanel in a tScrollBox to have scrolling functionality. To do so
  • we simply place the property_panel_ on a tScrollBox
  • after the creation of all the properties, we set the property_panel_.Height to the last g_y value, wich automatically triggers the scrolling if this value is greater than the tScrollBox.Height


And here is a snapshot after selecting the top-left "exit_" button in the "Selector" combobox, and changing its Align property to alLeft:

align_exit_button



2.5 - The Component UML Class Diagram

The components involved in the presentation can be described by the following UML Class Diagram:

inspector_uml_class_diagram



You may also have a look at the more complete Firemonkey Architecture Class Diagram, which also contains short component category descriptions



2.6 - What we learned

This showed us
  • that programming standard IDE stuff is just the same as it used to be with the VCL
  • a couple of minor points were raised, mainly
    • the tFmxObject.Children organization
    • tFmxObject.AddObject and tFmxObject.RemoveObject are used to manage those parent / child relationship.
    • RemoveObject does not Free the removed object. It simply removes it from the list
    • when freeing an object, we have to make sure that it no longer contains children. This can be done by setting a new my_child.Parent value
    • the tStyledObjects all have a child containing the style hierarchy. Removing this child removes the display of the styles of this control (no border, effects, animation etc)
    • the Parent is in charge of the display : the children's Positions, for instance, are relative to the Parent. And there are lots of conversions methods for absolute values. But the traditional Vcl clipping now requires the ClipChildren True value (the default is False)
    • the change in some notations
      • tLabel.Text (instead of Caption)
      • tCheckBox.IsChecked (instead of Checked). Same for IsVisible etc.
    • the Single type for all measures (Position, Scale etc)
    • the fully qualified notation for all enumerated.
    • notice that this fully qualification does NOT apply to tAlphaColor. The reason is that colors are not enumerated, but "enumerated looking" names which are in fact CONSTs.
    • in addition those names have the "A" prefix: clAred, clAblue. I read it somewhere but do not remember what the "A" stands for
  • more disturbing is the absence of some simple properties, like tLabel.Color. As we presented in the FireMonkey Styles article, those changes are performed using the styled controls style elements


As for the result, it certainly is no candidate for the "coolest Object Inspector" in the world. But for a 2 hour job, it seems that a 300 line Object Inspector is not that bad a result after all.




3 - Download the 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.
The .ZIP file(s) contain:

  • the main program (.DPROJ, .DPR, .RES), the main form (.PAS, .ASPX), and any other auxiliary form or files
  • any .TXT for parameters, samples, test data
  • all units (.PAS .ASPX and other) for units
Those .ZIP
  • are self-contained: you will not need any other product (unless expressly mentioned).
  • will not modify your PC in any way beyond the path where you placed the .ZIP (no registry changes, no path outside from the container 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.



4 - References

For RTTI
  • Delphi 2010 - RTTI & Attributes
      Robert LOVE - September 2009
        Robert is the "Delphi Rtti Guru". This link will present you a comprehensive introduction for RTTI. And he will also present several videos about RTTI at the 2011 CodeRage

  • Rtti.TRttiType Example
      example for displaying methods, properties etc from the Wiki Help
  • The Delphi XE2 demos at SourceForge also contain a RttiBrowser


We have a couple of articles about FireMonkey or Delphi XE2 :
  • FireMonkey Architecture : the basic tComponent <- tFmxObject <- Fmx.tControl <- tStyledControl hierarchy. Firemonkey UML Class diagram, and short feature description.
  • FireMonkey Styles changing styles for all or for some components, the Style Designer, content of a .STYLE file, setting then StyleLookup property, predefined styles.
  • FireMonkey Animations tutorial : selecting the Property to animate, the start and end values, the interpolation law, the speed and repetition. 3d animations. Vcl or FireMonkey ? (in French)
  • Delphi XE2 LiveBindings Tutorial : how to setup the SourceComponent and the ControlComponent and expression, tBindingsList, the bindings Editor, using several sources with tBindingScope, building bindings by code, LiveBindings and databases. Far more flexible than the Vcl db_xxx, but with the risks of late binding (in French)
  • Delphi LiveBindings Spelunking : analysis of the architecture of the Delphi LiveBindings : how the tBindingExpression compiles a String expression to build an environment referencing objects which can be evaluated to fill component properties. Dump of the pseudo code and UML Class Diagram of the LiveBinding architecture
  • FireMonkey Style Explorer : create tFmxObjects from their class name, create their default style, display their child style herarchy in a tTreeView, present each style element in an Object Inspector which can be used to change the property values.



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: oct-11. 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
    + colibri_helpers
    + delphi
    + firemonkey
      – firemonkey_architecture
      – firemonkey_obj_inspect
      – fmx_style_explorer
    + compilers
  + delphi_training
  + delphi_developments
  + sweet_home
  – download_zip_sources
  + links
Contacts
Site Map
– search :

RSS feed  
Blog