Delphi Object Inspector

Simple FireMonkey Object Inspector - Felix John COLIBRI.

  1. Microsoft Object Inspector
  • 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 :
    • A Simple FireMonkey Object Inspector
    • Reading and Writing Properties
    • Download the Sources

1 - Simple FireMonkey Object Inspector

Jul 3, 2015 - The Object Inspector is the connection between the visual appearance of your application, and the code that makes your application run. I think the best GUI design option here would be something similar to the trusty Delphi Object Inspector on the Main form, it would also be easier to maintain.

Delphi object inspector search 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:


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:
Constk_property_value_x= 70+ 2;
k_value_width= 100;
Varg_y: Single= 5;

Procedureadd_property(p_property_name, p_property_value: String);
Varl_c_property_name_label: tLabel;
l_c_value_rectangle: tRectangle;
l_c_property_value_text: tText;
l_height: Single;
Begin
WithForm1Do
Begin
l_height:= property_in_place_edit_.Height+ 2;

l_c_property_name_label:= tLabel.Create(Form1);
Withl_c_property_name_labelDo
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);
Withl_c_value_rectangleDo
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);
Withl_c_property_value_textDo
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_y+ l_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:

ProceduretForm1.handle_property_value_mousedown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Single);
Varl_c_sender_text: tText;
l_c_property_value_rectangle: tRectangle;
Begin
l_c_sender_text:= SenderAstText;
l_c_property_value_rectangle:= l_c_sender_text.ParentAstRectangle;

WithForm1, property_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:
ProcedureTForm1.add_name_value_Click(Sender: TObject);
Begin
add_property(property_name_edit_.Text, property_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
click on '888'
the in place tEdit is positioned, made visible and the value from the tText is transfered into the tEdit :



To transfer the value types in the in-place tEdit, we create a tEdit and use the tEdit.OnChange, or tEdit.OnKeyDown, like this:
ProcedureTForm1.property_in_place_edit_KeyDown(Sender: TObject; VarKey: Word;
VarKeyChar: Char; Shift: TShiftState);
Varl_c_property_text: tText;
Begin
IfKey= 13
ThenBegin
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:
Procedurefill_property_selector_combobox;
Varl_component_index: Integer;
Begin
WithForm1Do
Begin
property_selector_combobox_.Items.Clear;
Forl_component_index:= 0 ToComponentCount- 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
Procedurefill_object_inspector(p_component_name: String);
Varl_c_component: tComponent;

l_pt_property_list: PPropList;
l_property_count : Integer;
l_property_index : Integer;
l_property_name, l_property_value: String;
Begin
l_c_component:= Form1.FindComponent(p_component_name);
Ifl_c_componentIstFmxObject
ThenBegin
GetMem(l_pt_property_list, SizeOf(Pointer)* GetTypeData(l_c_component.ClassInfo)^.PropCount);

l_property_count := GetPropList(l_c_component.ClassInfo, tkProperties,
l_pt_property_list, True);
Forl_property_index := 0 Tol_property_count- 1 Do
Begin
l_property_name:= l_pt_property_list^[l_property_index].Name;
l_property_value:= GetPropValue(l_c_component, l_property_name, True);

add_property(l_property_name, l_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:

Procedureclear_object_inspector;
Varl_child_index: Integer;
l_c_fmx_object: tFmxObject;
Begin
WithForm1.property_panel_Do
Begin
l_child_index:= ChildrenCount- 1;
Whilel_child_index>= 0 Do
Begin
l_c_fmx_object:= Children[l_child_index];

If (l_c_fmx_objectIstLabel)
Or (l_c_fmx_objectIstRectangle)
ThenBegin
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:
    Procedurefill_object_inspector(p_component_name: String);
    Varl_c_component: tComponent;

    l_rtti_context: tRttiContext;
    l_rtti_type: tRttiType;
    l_c_rtti_property: tRttiProperty;
    l_property_name, l_property_value: String;
    Begin
    l_c_component:= Form1.FindComponent(p_component_name);
    Ifl_c_componentIstFmxObject
    ThenBegin
    l_rtti_type:= l_rtti_context.GetType(p_c_component.ClassType);
    Forl_c_rtti_propertyInl_rtti_type.GetPropertiesDo
    Begin
    l_property_name:= l_c_rtti_property.Name;
    Ifl_c_rtti_property.PropertyType.TypeKindIn
    [tkInteger, tkChar
    , tkFloat
    , tkString
    , tkWChar, tkLString, tkWString
    , tkEnumeration
    ]
    Thenl_property_value:=
    l_c_rtti_property.GetValue(p_c_component).ToString
    Elsel_property_value:= '?';

    add_property(l_property_name, l_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 :
    Proceduredetach_in_place_edit;
    // -- The tEdit becomes invisible
    Begin
    WithForm1.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:


2.5 - The Component UML Class Diagram

Microsoft Object Inspector

The components involved in the presentation can be described by the following 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 ClipChildrenTrue 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:
  • firemonkey_object_inspector_trial.zip building a simple name / value list(8 K)
  • simple_firemonkey_object_inspector.zip the Simple Firemonkey Object Inspector displaying the Form components Name and Value (8 K)
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
Delphi 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
  • 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 Object InspectorDelphi, FireBird, Tcp/IP, Web Services, OOP / UML, Design Patterns, Unit Testing training sessions.

In Delphi's object inspector, I see an asterisk behind a property name (ConnectionName*):

How does it get there, and above all: what does it mean?

In the sourcecode for TMySQLConnection I don't see anything special, so I guess it's some design-time thing?

update

It has something to do with the contents of the TSQLConnection.

To reproduce, paste the code below on a form.

After some playing around, I conclude that the asterisk appears when the Params property gets edited so that it doesn't have the default values any longer. It's still a mystery to me how this is achieved though.

Wouter van Nifterick
Wouter van NifterickWouter van Nifterick

1 Answer

You have appeared to have reverse engineered the meaning of the asterisk. Since I guess you have no source for the design time component code you'll need to rely on such reverse engineering, or any documentation that you can find.

In the comments you wonder how the component could cause the Object Inspector to display the asterisk. In order to do so the component would register a property editor that overrides TPropertyEditor.GetName. By doing so it can return any name it fancies and the Object Inspector will faithfully display that name.

To illustrate I've taken one of my own property editors, and hacked it around like so:

And now the properties that are served by this property editor appear like this in the Object Inspector:

So it seems almost certain to me that this is how the component you are working with is effecting this. The design time code will use the state of the component to determine whether or not to append the asterisk.

David HeffernanDavid Heffernan

Not the answer you're looking for? Browse other questions tagged delphidelphi-xe5delphi-ideobject-inspector or ask your own question.

Posted on