Menu
Object Inspector When used, our VCL Vertical Grid allows you to display and modify the properties of any object on your form at runtime. The Control includes a 'Default Editors Collection' option, which forces the Control to use custom editors to represent individual object values (for instance, a CheckEdit is used to display Boolean values). If you select a button at design time and look at the Object Inspector you'll notice several 'Parent-aware' properties. The ParentFont, for example, indicates whether the Font used for the Button's caption is the same as the one used for the Button's parent (in the previous example: Panel1). If ParentFont is True for all Buttons on a Panel. Board index » delphi » Object Inspector D2005. Delphi Developer. Sat, 09 Feb 2008 22:12:25 GMT. Object Inspector D2005. I was used to finding things in D7 Object inspector in alphabetical order. In D2005, I find no rhyme nor reason for the layout but I'm willing to learn.
- Delphi Object Inspector Font Size
- Delphi Object Inspector Component
- Delphi Object Inspector
- Delphi Object Inspector Source Code
Go Up to Classes and Objects Index
This topic covers the following material:
- Declaration syntax of classes
- Inheritance and scope
- Visibility of class members
- Forward declarations and mutually dependent classes
- 2Inheritance and Scope
- 3Visibility of Class Members
Class Types
A class, or class type, defines a structure consisting of fields, methods, and properties. Instances of a class type are called objects. The fields, methods, and properties of a class are called its components or members.
- A field is essentially a variable that is part of an object. Like the fields of a record, fields of classes represent data items that exist in each instance of the class.
- A method is a procedure or function associated with a class. Most methods operate on objects, that is, instances of a class. Some methods (called class methods) operate on class types themselves.
- A property is an interface to data associated with an object (often stored in a field). Properties have access specifiers, which determine how their data is read and modified. From other parts of a program outside of the object itself, a property appears in most respects like a field.
Objects are dynamically allocated blocks of memory whose structure is determined by their class type. Each object has a unique copy of every field defined in the class, but all instances of a class share the same methods. Objects are created and destroyed by special methods called constructors and destructors.
A variable of a class type is actually a pointer that references an object. Hence more than one variable can refer to the same object. Like other pointers, class-type variables can hold the value nil. But you do not have to explicitly dereference a class-type variable to access the object it points to. For example,
SomeObject.Size := 100
assigns the value 100 to the Size
property of the object referenced by SomeObject
; you would not write this as SomeObject^.Size := 100
.A class type must be declared and given a name before it can be instantiated. (You cannot define a class type within a variable declaration.) Declare classes only in the outermost scope of a program or unit, not in a procedure or function declaration.
A class type declaration has the following form:
Required elements of the class type declaration
className
is any valid identifier.memberList
declares members of the class: fields, methods, and properties.
![Delphi object inspector training Delphi object inspector training](https://3.bp.blogspot.com/-O26vVH48Qms/USexpoQSFLI/AAAAAAAACew/Vr55eBrcBp0/s1600/New+Picture+%282%29.jpg)
Optional elements of the class type declaration
abstract
. An entire class can be declared abstract even if it does not contain any abstract virtual methods.sealed
. A sealed class cannot be extended through inheritance.ancestorClass
. The new class inherits directly from the predefined System.TObject class, in case you omit (ancestorClass). If you include (ancestorClass), and memberList is empty, you can omit end.nestedTypeDeclaration
. Nested types present a way to keep conceptually related types together and to avoid name collisions.nestedConstDeclaration
. Nested constants present a way to keep conceptually related types together and to avoid name collisions.
A class cannot be both
abstract
and sealed
. The [abstract | sealed]
syntax (the [ ]
brackets and the |
pipe between them) is used to specify that only one of the optional sealed
or abstract
keywords can be used. Only the sealed
or abstract
keywords are meaningful. The brackets and pipe symbols should be deleted.Note: Delphi allows instantiating a class declared abstract, for backward compatibility, but this feature should not be used anymore.
Methods appear in a class declaration as function or procedure headings, with no body. Defining declarations for each method occur elsewhere in the program.
For example, here is the declaration of the
TMemoryStream
class from the Classes
unit:Classes.TMemoryStream
descends from Classes.TCustomMemoryStream
, inheriting most of its members. But it defines – or redefines – several methods and properties, including its destructor method, Destroy
. Its constructor, Create
, is inherited without change from System.TObject
, and so is not redeclared. Each member is declared as private, protected, or public (this class has no published members). These terms are explained below.Given this declaration, you can create an instance of
TMemoryStream
as follows:Inheritance and Scope
When you declare a class, you can specify its immediate ancestor. For example:
declares a class called TSomeControl that descends from Vcl.Controls.TControl. A class type automatically inherits all of the members from its immediate ancestor. Each class can declare new members and can redefine inherited ones, but a class cannot remove members defined in an ancestor. Hence TSomeControl contains all of the members defined in Vcl.Controls.TControl and in each of the Vcl.Controls.TControl ancestors.
The scope of a member's identifier starts at the point where the member is declared, continues to the end of the class declaration, and extends over all descendants of the class and the blocks of all methods defined in the class and its descendants.
TObject and TClass
The System.TObject class, declared in the System unit, is the ultimate ancestor of all other classes. System.TObject defines only a handful of methods, including a basic constructor and destructor. In addition to System.TObject, the System unit declares the class reference type System.TClass:
If the declaration of a class type does not specify an ancestor, the class inherits directly from System.TObject. Thus:
is equivalent to:
The latter form is recommended for readability.
Compatibility of Class Types
A class type is assignment-compatible with its ancestors. Hence a variable of a class type can reference an instance of any descendant type. For example, given the declarations:
the variable Fig can be assigned values of type TFigure, TRectangle, and TSquare.
Object Types
The Delphi compiler allows an alternative syntax to class types. You can declare object types using the syntax:
where objectTypeName is any valid identifier, (ancestorObjectType) is optional, and memberList declares fields, methods, and properties. If (ancestorObjectType) is omitted, then the new type has no ancestor. Object types cannot have published members.
Since object types do not descend from System.TObject, they provide no built-in constructors, destructors, or other methods. You can create instances of an object type using the New procedure and destroy them with the Dispose procedure, or you can simply declare variables of an object type, just as you would with records.
Object types are supported for backward compatibility only. Their use is not recommended.
Visibility of Class Members
Every member of a class has an attribute called visibility, which is indicated by one of the reserved words private, protected, public, published, or automated. For example,
declares a published property called Color. Visibility determines where and how a member can be accessed, with private representing the least accessibility, protected representing an intermediate level of accessibility, and public, published, and automated representing the greatest accessibility.
If a member's declaration appears without its own visibility specifier, the member has the same visibility as the one that precedes it. Members at the beginning of a class declaration that do not have a specified visibility are by default published, provided the class is compiled in the {$M+} state or is derived from a class compiled in the {$M+} state; otherwise, such members are public.
For readability, it is best to organize a class declaration by visibility, placing all the private members together, followed by all the protected members, and so forth. This way each visibility reserved word appears at most once and marks the beginning of a new 'section' of the declaration. So a typical class declaration should be like this:
You can increase the visibility of a property in a descendent class by redeclaring it, but you cannot decrease its visibility. For example, a protected property can be made public in a descendant, but not private. Moreover, published properties cannot become public in a descendent class. For more information, see Property Overrides and Redeclarations.
Private, Protected, and Public Members
A private member is invisible outside of the unit or program where its class is declared. In other words, a private method cannot be called from another module, and a private field or property cannot be read or written to from another module. By placing related class declarations in the same module, you can give each class access to the private members of another class without making those members more widely accessible. For a member to be visible only inside its class, it needs to be declared strict private.
A protected member is visible anywhere in the module where its class is declared and from any descendent class, regardless of the module where the descendent class appears. A protected method can be called, and a protected field or property read or written to, from the definition of any method belonging to a class that descends from the one where the protected member is declared. Members that are intended for use only in the implementation of derived classes are usually protected.
A public member is visible wherever its class can be referenced.
Strict Visibility Specifiers
In addition to private and protected visibility specifiers, the Delphi compiler supports additional visibility settings with greater access constraints. These settings are strict private and strict protected visibility.
Class members with strict private visibility are accessible only within the class in which they are declared. They are not visible to procedures or functions declared within the same unit. Class members with strict protected visibility are visible within the class in which they are declared, and within any descendent class, regardless of where it is declared. Furthermore, when instance members (those declared without the class or class var keywords) are declared strict private or strict protected, they are inaccessible outside of the instance of a class in which they appear. An instance of a class cannot access strict private or strict protected instance members in other instances of the same class.
Note: The word strict is treated as a directive within the context of a class declaration. Within a class declaration you cannot declare a member named 'strict', but it is acceptable for use outside of a class declaration.
Published Members
Published members have the same visibility as public members. The difference is that run-time type information (RTTI) is generated for published members. RTTI allows an application to query the fields and properties of an object dynamically and to locate its methods. RTTI is used to access the values of properties when saving and loading form files, to display properties in the Object Inspector, and to associate specific methods (called event handlers) with specific properties (called events).
Published properties are restricted to certain data types. Ordinal, string, class, interface, variant, and method-pointer types can be published. So can set types, provided the upper and lower bounds of the base type have ordinal values from 0 through 31. (In other words, the set must fit in a byte, word, or double word.) Any real type except Real48 can be published. Properties of an array type (as distinct from array properties, discussed below) cannot be published.
Some properties, although publishable, are not fully supported by the streaming system. These include properties of record types, array properties of all publishable types, and properties of enumerated types that include anonymous values. If you publish a property of this kind, the Object Inspector will not display it correctly, nor will the property's value be preserved when objects are streamed to disk.
All methods are publishable, but a class cannot publish two or more overloaded methods with the same name. Fields can be published only if they are of a class or interface type.
A class cannot have published members unless it is compiled in the {$M+} state or descends from a class compiled in the {$M+} state. Most classes with published members derive from Classes.TPersistent, which is compiled in the {$M+} state, so it is seldom necessary to use the $M directive.
Note: Identifiers that contain Unicode characters are not allowed in published sections of classes, or in types used by published members.
Automated Members (Win32 Only)
Automated members have the same visibility as public members. The difference is that Automation type information (required for Automation servers) is generated for automated members. Automated members typically appear only in Win32 classes . The automated reserved word is maintained for backward compatibility. The TAutoObject class in the ComObj unit does not use automated.
The following restrictions apply to methods and properties declared as automated.
- The types of all properties, array property parameters, method parameters, and function results must be automatable. The automatable types are Byte, Currency, Real, Double, Longint, Integer, Single, Smallint, AnsiString, WideString, TDateTime, Variant, OleVariant, WordBool, and all interface types.
- Method declarations must use the default register calling convention. They can be virtual, but not dynamic.
- Property declarations can include access specifiers (read and write) but other specifiers (index, stored, default, and nodefault) are not allowed. Access specifiers must list a method identifier that uses the default register calling convention; field identifiers are not allowed.
- Property declarations must specify a type. Property overrides are not allowed.
The declaration of an automated method or property can include a dispid directive. Specifying an already used ID in a dispid directive causes an error.
On the Win32 platform, this directive must be followed by an integer constant that specifies an Automation dispatch ID for the member. Otherwise, the compiler automatically assigns the member a dispatch ID that is one larger than the largest dispatch ID used by any method or property in the class and its ancestors. For more information about Automation (on Win32 only), see Automation Objects.
Forward Declarations and Mutually Dependent Classes
If the declaration of a class type ends with the word class and a semicolon—that is, if it has the form
with no ancestor or class members listed after the word class, then it is a forward declaration. A forward declaration must be resolved by a defining declaration of the same class within the same type declaration section. In other words, between a forward declaration and its defining declaration, nothing can occur except other type declarations.
Forward declarations allow mutually dependent classes. For example:
Do not confuse forward declarations with complete declarations of types that derive from System.TObject without declaring any class members.
See Also
Retrieved from 'http://docwiki.embarcadero.com/RADStudio/Sydney/e/index.php?title=Classes_and_Objects_(Delphi)&oldid=271889'
Brian Long (www.blong.com)
- What Is RTTI?
- What Can We Do With RTTI?
- Common properties with no common ancestor
If you find this article useful then please consider making a donation. It will be appreciated however big or small it might be and will encourage Brian to continue researching and writing about interesting subjects in the future.
Introduction
All versions of Delphi have supported the generation of run-time type information, or RTTI. Support for this has been added first and foremost to allow the design-time environment to do its job, but developers can also take advantage of it to achieve certain code simplifications. Unfortunately, it has not been made entirely obvious how to use this information as the RTTI has for the most part remained undocumented. This paper attempts to lift the mystique from RTTI and move it from the realms of the propeller head into the reach of the average Delphi user.
What Is RTTI?
RTTI is implemented as data structures generated by the compiler whilst compiling a program. The general idea is to allow information describing various categories of types to be available at run-time for examination and indeed more productive purposes. Normally, types are considered to only be available at compile-time, used by the compiler to identify storage requirements, to ensure appropriate values are used with various variables and so on. RTTI means we have extra information available to query for our own purposes.
Why Do We Have RTTI?
To run the risk of stating the obvious, the Delphi design-time environment allows you to visually design forms that are used in your programs at run-time. It stores the form description in a binary .DFM file, including the type names of all the components placed on the form and also the names and (non-default) values of all form and component properties. At run-time your form file (which was turned into a resource in your EXE during compilation) is read and your EXE magically creates all the objects giving them all the required property values.
In order to get from a string, signifying a class name, to a created object of the required type requires RTTI (or a humungous case statement, which would be a nightmare to maintain). Also, in order to set the values of the named properties in the created objects, bearing in mind they can be of many types, also requires RTTI.
Additionally, the is and as keywords rely on the presence of some form of RTTI to identify if a given object reference is defined in terms of a supplied class type, or one of its descendants. If no information on types was present at run-time then identifying ancestors of any types would be rather tricky.
Delphi Object Inspector Font Size
How Do We Get RTTI?
When designing forms, references to all the objects placed on the form are inserted at the top of the form class definition. This top section has no visibility specifier on show, but is considered to be published. Entries in the published and public sections are equally accessible at run-time. The principal difference between published and public is that published items of a component appear in the Object Inspector at design-time. This happens because RTTI is automatically generated for the types used to define data fields in the published section. The Object Inspector picks this up and uses it to identify what to add to its list of properties and events.
To access the RTTI for a class, you can access its ClassInfo method. This returns a pointer to an RTTI block as generated by the compiler. For any other arbitrary type (that the compiler supports generating it for) you can pass the type to the TypeInfo function.
TypeInfo is one of those special functions that gets evaluated by the compiler at compile time. If you pass a type identifier to TypeInfo (as opposed to a class reference variable that would need to be evaluated at run-time) then, presuming it is an appropriate type, at run-time you will have some RTTI for that type to play with. If the type is a class, then the RTTI will exist in your executable anyway, but for other types such as enumerated types and sets, it won’t necessarily exist.
Types not appropriate for passing to TypeInfo include any pointer types (e.g. PChar, PWideChar, PString or Pointer), types defined locally in a subroutine, array types and record types. Delphi 3 enhances the situation by supporting arrays, records and interfaces, although array or record properties are still not supported in component published sections. Delphi 4 goes forward with 64-bit integers and dynamic arrays. Type information will exist for a given type if a call to TypeInfo is made for it, or if the type is referred to in the published section of a class that is referenced in the program source. If type information exists, it will not be duplicated. Calling TypeInfo a second time on one type will return a pointer to the same RTTI table.
Calling an object’s or class’s ClassInfo method performs the same job as passing it to the TypeInfo function. It returns a pointer to an object’s RTTI. The only real difference is that TypeInfo is evaluated at compile time. The implication of this fact is that you must pass TypeInfo an actual type, rather than a class reference variable. The net result is that TypeInfo will be more efficient if you know the type.
How Do We Talk To RTTI?
It has been mentioned that both TypeInfo and TObject.ClassInfo return a pointer to a compiler-generated RTTI block. In order to make use of RTTI we need to know the structure of this table. Fortunately, Borland have supplied a unit which defines it and also has various routines to give us access to it. The TypInfo unit had the interface section supplied in Delphi 1, but Delphi 2 onwards supply the whole unit (if you have the VCL source). However Inprise has offered no other documentation on this unit, apart from the calls to its routines that are scattered through the VCL. The interface file in Delphi 1 also had a stern message alerting you to the dangers of its use: Warning: The interface section of this file will change in future versions of Delphi.
The unit defines a number of types, many of which have fields that have been commented out. This is because the types are there to try and provide a Pascal framework around the RTTI that the compiler generates in a space-efficient format.
Let’s start at the beginning. Both the TypeInfo pseudo-function and the ClassInfo class method are defined to return a simple Pointer, but they really return a PTypeInfo pointer. This is a pointer to the most important type in the unit, the TTypeInfo record, which looks like this:
Kind is a value from the TTypeKind enumerated type, which gets four additional values in Delphi 2, another three in Delphi 3 and a pair more in Delphi 4. TTypeKind rather dictates the sorts of general types that can have RTTI:
TTypeKind value | Types that yield this value |
tkUnknown | A place holder value. Never used |
tkInteger | Used for any ordinal type and sub-range types |
tkChar | Char and AnsiChar types (where Char and AnsiChar are synonyms) |
tkEnumeration | All enumerated types. This includes Boolean, ByteBool, WordBool, LongBool and Bool |
tkFloat | Any floating point type except Real, which explains why Real properties are not fully supported |
tkString | Old-style string types, e.g. String[12] and ShortString |
tkSet | Set types |
tkClass | Class types |
tkMethod | Procedure and function method types |
tkWChar | WideChar type, new in Delphi 2 |
tkLString | Delphi 2+ long strings (made of AnsiChars) |
tkLWString | Delphi 2 constant added in preparation of long wide strings for Unicode, which were planned for Delphi 3 |
tkWString | The Delphi 3 constant which replaces tkLWString |
tkVariant | Variant type, new in Delphi 2 |
tkArray | Array types, new in Delphi 3 |
tkRecord | Microsoft office standard 2019 v16 34. Record types, new in Delphi 3 |
tkInterface | Interface types, new in Delphi 3 |
tkInt64 | 64-bit integers, new in Delphi 4 |
tkDynArray | Dynamic array types, new in Delphi 4 |
To get the type name of an appropriate type at run-time we can use something like:
The last TTypeInfo field, TypeData, is commented out because the Name field (a string holding the type name) is not what it seems. It is implemented as an old-style Pascal short string, but for space efficiency only takes up as many bytes as are required for the type name, rather than a fixed number (which would leave several unused characters). Therefore the position of the TypeData field is variable.
If we want to get access to the TypeData field we have to fiddle around somewhat, setting up a pointer to the relevant position in the record after using the length of Name to identify where it is. The TypInfo unit provides a routine to do this as we will see later.
Delphi Object Inspector Component
The TTypeData record is where things get interesting/messy (delete as appropriate). It is a large variant record with different fields being used depending on the value of the TTypeInfo.Kind field. Let’s look at a simplified view of it.
In the case of a long or wide string or variant there is no additional type information recorded. For a floating point type, a value from the TFloatType appears that specifies what variety it is.
There are two thing to notice here. Firstly, the ftCurr symbol was new in Delphi 2 to denote the Currency type.
Delphi Object Inspector
Secondly, there is no symbol to represent the Real type, since it’s use was meant to be dwindling. In Delphi 1 to 3 we were advised not to use type Real, as it was a proprietary data format and nothing other than Delphi understood it. The fact that no type information float type symbol exists for Real explains why published properties of type Real are not supported - RTTI cannot be generated for them. In Delphi 4, Real is now defined to be the same as Double. So Real can be used for published properties and will have an associated float type symbol of ftDouble. However if Delphi 4 programmers really want to use the old 6-byte Real format they can either use type Real48, or use the $RealCompatibility compiler directive.
In the case of a short string, the maximum length is recorded, and for ordinal types, class types, method types and interface types, more information is stored.
All ordinal types have a field OrdType of type TOrdType which says how many bytes are required and whether signed values are supported.
Note that Delphi 3’s implementation of Cardinal (ostensibly an unsigned four byte number) is implemented as a Longint without the negative values (i.e. a 31-bit unsigned number). It uses otSLong. Delphi 4 extends Cardinal to be a true 32-bit unsigned number, however the OrdType value is still otSLong.
If the type is not a set, the minimum and maximum values are available in terms of Longints and if it is an enumerated type you can gain access to all the enumerated type value names. There is also a field called BaseType which presumably is meant to point to the RTTI table for the enumerated type used as a basis for this one, but I have been unable to get it to point to anything other than the same type’s RTTI.
If the type is a set, you can get to the RTTI table for the set’s base type - the ordinal type whose values can be in a set of this type. Note that the BaseType field for an enumerated type mentioned just above and the CompType field for a set are PTypeInfo’s in Delphi 1 and 2 (pointers to TTypeInfo records) but are defined as PPTypeInfo’s in Delphi 3 onwards (pointers to PTypeInfo record pointers). This should, in theory require conditional compilation but in fact you can forget about the extra level of indirection in Delphi 3+. It has been the case since Delphi 2 came out that if a de-reference operation was required (i.e. a caret, ^, sign was needed) you could dispense with it if there would be no ambiguity as to what was intended.
RTTI for a class type includes a class reference, a pointer to the ancestor class’s RTTI table (again defined as PTypeInfo or PPTypeInfo depending on which version), the number of published properties and the name of the defining unit. Again, this name is only as long as it needs to be, hence the commented field that follows. In addition, there is a TPropData record that follows containing data about all the published properties.
This time the commented out field at the end can be accessed because it is not preceded by an indeterminately long string. However because this PropList field is supposed to be a variably sized array, its declaration is commented and substituted with an empty record. PropCount dictates how many TPropInfo records there are, and each TPropInfo record contains all the characteristics of a published property.
We have a pointer to the RTTI table for the property’s type, a pointer to the property reader and property writer methods. Additionally there is a reference to the method, field or value that is used to determine whether to store the property value in the form file (specified with the stored directive), the property index (specified with the index directive), a default value (specified by the default directive, which is also used to dictate whether the property gets stored in the form file) and its name.
Information about published method is also quite full. We can find out if the method is a procedure or function, and how many parameters it has. Delphi 3 added new values to the TMethodKind type (mkSafeProcedure and mkSafeFunction) to represent routines compiled with the new safecall directive as used in dual interface declarations and implementations. Delphi 4 added several more: mkConstructor, mkDestructor, mkClassProcedure, and mkClassFunction. ParamCount specifies also how many parameter description records (the commented out record below) exist, and each one of those has an indication of whether the parameter has a var or const prefix, if it is an array, and also what the parameter’s name and type name are. Again, Delphi 3 extended the TParamFlags type with pfAddress, pfReference and pfOut for the new parameter types supported in interfaces.
Once you see the extent of the information recorded in RTTI (automatically for types used in published component sections), you can begin to get an idea of how the Object Inspector does its job. It knows how to let you edit values, because it can find out all the details about the type. The Events page can manufacture event handlers because event properties have a tkMethod type identifier, and Delphi can find out what sort of event handler to manufacture.
The TTypeData record has a lot of useful stuff in, but of course with all these non-standard strings dotted here and there, accessing the various constituent records proves a bit tricky. There are a number of routines in the TypInfo unit to help us out, as explained below.
What Can We Do With RTTI?
The routines available in the TypInfo unit are much the same in the three current versions of Delphi, although in some cases where strings are returned they differ. In Delphi 1, pointers to strings are sometimes returned, but in Delphi 2 and 3 the strings themselves are always returned.
The routines tend to take either a PTypeInfo or PPropInfo pointer - a pointer to an RTTI table or to a property information record as described above.
Remember that a PTypeInfo pointer can be obtained both from the TypeInfo function for most types and from the ClassInfo class method for object references and class references. It’s interesting that to make things simple, the TTypeData record for a class type also has ClassType field that points back to the class reference.
We can gain easy access to the TTypeData record using GetTypeData which returns a pointer to it:
so to show the unit in which the class of the main form was defined in (by default, this would be Unit1), we can use:
To be more generic, bearing in mind the form's class name may have changed, we could use:
Since all of the TypInfo routines that operate on properties want a PPropInfo, there are several ways of getting one. GetPropInfo gets a TPropInfo record for a named property. GetPropInfos gets TPropInfos for all published properties in the class and its ancestors, and puts them in a TPropList array. Lastly, GetPropList will get all the properties whose types match the values in the set parameter TypeKinds and return how many it found. If you pass tkProperties as this parameter, it acts much the same as GetPropList (although GetPropList does not return how many properties were found - you have to use TTypeData.PropCount instead).
For example, to get the names of all form properties in a listbox called ListBox1, you could write:
or perhaps like the following which would only take as much memory as is required:
Reading and writing arbitrary properties
There are five pairs of routines (four in Delphi 1) for reading and writing property values of a given name (a sixth was added in Delphi 4):
TypInfo routine | Properties to use them with |
GetOrdProp,SetOrdProp | ordinal or class properties |
GetStrProp,SetStrProp | long or short string properties |
GetFloatProp,SetFloatProp | floating point properties |
GetVariantProp,SetVariantProp | variant properties (not present in Delphi 1) |
GetMethodProp,SetMethodProp | method properties (events) |
GetInt64Prop,SetInt64Prop | 64-bit Integer properties (added in Delphi 4) |
The Get/SetOrdProp routines both take an object reference and a pointer to a property information record and they either take or return a Longint. A four-byte Longint is big enough to represent any Pascal ordinal value and also any object reference and so you use a typecast to translate between your value of interest and the Longint.
The other routines are laid out in a similar way, but take or return a value of an appropriate type.
These routines are used by the VCL streaming mechanics to read and write form files. Once a property value is read from a form file, information is looked up (a TPropInfo record) and its type category is identified (TTypeKind). Then the relevant property writing routine is invoked to set the property value with the data read in from the form file.
Common properties with no common ancestor
You can often come up against a requirement to set a property in many components where there is no common ancestor that defines this property. Let’s look at an example or two to emphasise the problem and see how RTTI can offer us a solution.
Example 1 - the Enabled property
Consider this scenario. You have a form with a number of controls on that all need to be disabled by having their Enabled property set to False. The controls might be a button, an edit control and a check box, but then again may be of some other types. You could implement a routine called DisableThem as shown below that disables any controls.
Now consider that you also need to disable some menu items, which also have an Enabled property. The problem here is that the TMenuItem class adds Enabled as a new property of its own - TMenuItem is derived from TComponent that does not have an Enabled property. On the other hand, the TButton, TEdit and TCheckBox class all inherit the Enabled property from their common ancestor type TControl.
So the Enabled property of the TMenuItem and that of all the other classes are actually different properties - they just have the same names. This means we have to use a different coding approach.
If we need to address other components with unique Enabled properties, this code will lengthen. A possibly better approach that takes advantage of the SetOrdProp routine from the TypInfo unit follows.
The projects Generic.Dpr and Genric2.Dpr that accompany this paper shows this same idea in a couple of ways, one more generic than the other.
Delphi Object Inspector Source Code
Example 2 - DataSource and DataField properties
The same sort of problem arises when trying to set up>DataSource property, and most of them have a DataField property. However each control adds these properties individually - none of them come from a common ancestor. We can take the same approach as above to set these properties en masse. This routine deals with the DataSource property.
Converting between enumerated type values and strings
Enumerated types are programmatic devices to make programs easier to read and write by substituting recognisable terms for numeric values (which enumerated types are implemented as). It is normally the case that at compile time, all information about these textual terms is removed from the executable, but RTTI can change that.
Given a type:
It is possible to start with a string ‘Tuesday’ and get a TDayOfWeek value Tuesday out. It is also possible to do the reverse. The two routines GetEnumName and GetEnumValue do this and to work with them requires a typecast between any enumerated value and an Integer.
Given the enumerated type TFormBorderStyle, used for a form’s BorderStyle property and defined as:
then we can say:
Notice that in Delphi 2 and 3 you do not use the ^ symbol in conjunction with GetEnumName.
You can find code like this in the projects EnumEg.Dpr and EnumEg2.Dpr that accompany this paper.
Setting a component’s default properties
When writing components you are able to specify default property values in the class declaration. This does not actually set the properties of any component instances to those defaults - that must still be done in the component constructor. Instead, it simply sets up one of the fields in the component’s RTTI block. The Delphi environment uses this information to reduce the volume of information that must go in the .DFM form file. Only properties whose values differ from any specified default go in the file.
As mentioned just above, it is still down to the developer to manually set the properties to their default values in the constructor. This can be a bit of a chore to do, particularly if there are several of them. To simplify the task we can use the RTTI information, looping through all the published properties that might have a default value and apply it. The code shown here (and available to test out in the PropEg.Pas component unit and RttiUnit.Pas utility unit) is based on code from Secrets Of Delphi 2 by Ray Lischner, published by The Waite Group.
Copying properties from one component to another
Another possible use for RTTI is for helping to copy one object to another. Delphi doesn’t provide much in the way of support for copying objects apart from the Assign method, which must be coded specifically for each class in question. If you want to copy the values of common properties from one component to another then RTTI can help. Additionally, if you make up new classes, publishing properties that represent the important data will facilitate easy object copying if it ever becomes necessary.
The RTTIUnit.pas unit that accompanies this paper has a routine called CopyObject defined in it. CopyObject takes two object references - a source object and a target object. It iterates through all the published properties in the source object and attempts to set the value of the same property in the target. If the property doesn’t exist in the target then it skips onto the next one.
Given a bitmap button, a normal button and an edit control on a form, you could use the following statements to ensure that all common properties of these objects are exact copies of each other using the following statements. Of course, given that the component co-ordinates are properties themselves, then the last two statements ensures that they don’t all end up sitting on top of each other.
The code is a bit too lengthy to list here but isn’t really that far removed from the default property setting code above. Refer to the unit for the source. Also, the sample project CopyEg.Dpr supplied with this paper shows an example of the function in use.
Summary
This paper has discussed the subject of Run-Time Type Information. After looking at why it exists we then looked at how we can access it and a number of applications of how it can be used. You can use it in many different ways to those suggested here, and hopefully you have been given a taster of how this remarkably under-used feature of Delphi can be employed.
About Brian Long
Brian Long used to work at Borland UK, performing a number of duties including Technical Support on all the programming tools. Since leaving in 1995, Brian has spent the intervening years as a trainer, trouble-shooter and mentor focusing on the use of the C#, Delphi and C++ languages, and of the Win32 and .NET platforms. In his spare time Brian actively researches and employs strategies for the convenient identification, isolation and removal of malware. If you need training in these areas or need solutions to problems you have with them, please get in touch or visit Brian's Web site.
Brian authored a Borland Pascal problem-solving book in 1994 and occasionally acts as a Technical Editor for Wiley (previously Sybex); he was the Technical Editor for Mastering Delphi 7 and Mastering Delphi 2005 and also contributed a chapter to Delphi for .NET Developer Guide. Brian is a regular columnist in The Delphi Magazine and has had numerous articles published in Developer's Review, Computing, Delphi Developer's Journal and EXE Magazine. He was nominated for the Spirit of Delphi award in 2000.