1. Home
  2. Computing & Technology
  3. Delphi Programming
RTL reference|Glossary|Tips/Tricks|FREE App/VCL|Best'O'Net|Books|Link To
 
Creating Custom Delphi Components, Part III
Page 3: The property editor; Registering property editors
 More of this Feature
• Page 1: Custom Editors
• Page 2: Component Editors
• Page 4: Dialog Editors

• Download Demo Projects
 Join the Discussion
"Post your questions, concerns, views and comments to this article..."
Discuss!
 Related Resources
• Custom VCL development
• VCL with source
• Third party VCL
• VCL using

   The property editor
Property editors are used by the IDE to allow special editing of individual properties within a component. Some editors are very simple, some are much more complicated. Delphi already has a number of standard property editors, some of these are:

.TIntegerProperty. Used for inputting integers.
.TCharProperty. Used for inputting a single character.
.TEnumProperty. Used for selected an individual element in an enumerated type (alTop, alClient etc).
.TBoolProperty. Used for selecting "True" or "False" for Boolean properties.
.TFloatProperty. Used for inputting floating point numbers (Variable type Float / Extended etc. The "Real" type should not be used for component properties)
.TStringProperty. Used for inputting strings up to a maximum of 255 characters.
.TSetProperty. Used for including / excluding individual elements of a Set property. Each element is displayed as a Boolean sub-property. Setting the value to "True" includes the element, setting it to "False" excludes it.
.TClassProperty. This is the base class to descend from when you want to create a custom editor to be invoked for properties of a certain class (when you have a class as a property, such as TImage.Picture).

All of these property editors descend directly or indirectly from TPropertyEditor. TPropertyEditor has many properties and methods, the most significant are.

function AllEqual: Boolean; virtual;
function GetAttributes: TPropertyAttributes; virtual;
procedure Edit; virtual;
function GetValue: string; virtual;
procedure GetValues(Proc: TGetStrProc); virtual;

AllEqual
When multiple components are selected the object inspector filters its list of properties to only the ones that all the selected components have in common. If the value in each component for any given property (eg Width) is the same, the value will be displayed, otherwise no value will be shown. AllEqual is the routine that determines if each value is identical.

function TStringProperty.AllEqual: Boolean;
var
  I: Integer;
  V: string;
begin
  Result := False;
  if PropCount > 1 then
  begin
    V := GetStrValue;
    for I := 1 to PropCount - 1 do
      if GetStrValueAt(I) <> V then Exit;
  end;
  Result := True;
end;

In the above example TStringProperty compares each value (using GetStrValueAt) with the value of the first component in the list (using GetStrValue, GetStrValueAt(0) would have done the same). The size of the list is determined by using PropCount, this returns the total amount of components selected.

GetAttributes
GetAttributes is called by the IDE when it needs to gather information about the property editor. The object inspector displays an appropriate editor based on the information supplied. The result of GetAttributes (TPropertyAttributes) is a set, so it may contain a combination of the following values (this is not a complete list)

paDialog
Tells the object inspector to show a […] button after the property name, when the user clicks this button the Edit method is triggered.

paSubProperties
Tells the object inspector to show a [+] expand button before the property name, clicking this button will show an expanded list of sub properties (usually the published properties of a class property).

paValueList
The object inspector will show a combobox with a list of values, this list is determined by the IDE by calling the GetValues method.

NOTE: The GetValues method, not the GetValue method which is completely different

paSortList
If combined with paValueList, the values displayed will be sorted alphabetically.

paMultiSelect
This specifies to the IDE that the property is allowed to be displayed when multiple components are selected. This item is not present for editors such a TClassProperty.

paAutoUpdate
Causes the SetValue method to be called each time the value is altered within the object inspector, rather than waiting for the user to press or edit another property. This is used for "Caption" and "Text" properties, to give a live representation of the value the user is entering.

paReadOnly
If this element is included the value in the object inspector is read-only. This is typically used in conjunction with paDialog. GetValue would be overridden to return a descriptive representation of the property.

Edit
This method is called when the […] button for the property is clicked. This button appears if the paDialog element is included within the result of GetAttributes.

GetValue
This method is called when the object inspector needs to know how to display the property as a string. This is typically used when [paDialog, paReadOnly] are specified within the result of GetAttributes.

GetValues
This method is called when the object inspector needs to retrieve a list of values to display when paValueList is specified within the result of GetAttributes.

GetValues passes a parameter called "Proc" which is of type TGetStrProc. GetStrProc is declared as TGetStrProc = procedure(const S: string) of object;

The IDE expects "Proc" to be called once for every value that should be displayed in the object inspector for this property.

procedure THintProperty.GetValues(Proc: TGetStrProc);
begin
  Proc('First item to display');
  Proc('Second item to display');
end;

The following example shows how to provide a list of default values for the "Hint" property of all components, whilst still allowing the user to enter a value not in the list.

type
  THintProperty = class(TStringProperty)
  public
    function GetAttributes: TPropertyAttributes; override;
    procedure GetValues(Proc: TGetStrProc); override;
  end;

procedure Register;

implementation

procedure Register;
begin
  RegisterPropertyEditor(TypeInfo(String), nil, 'Hint', THintProperty); 
end;

{ THintProperty }

function THintProperty.GetAttributes: TPropertyAttributes;
begin
  Result := inherited GetAttributes + [paValueList, paSortList];
end;

procedure THintProperty.GetValues(Proc: TGetStrProc);
begin
  Proc('This is a required entry');
  Proc('Press F1 for more information');
  Proc('This value is read-only');
end;

First GetAttributes is overridden, and [paValueList, paSortList] are included in the result. Next GetValues is overridden and three values are added to the drop down list by calling the "Proc" procedure.

   Registering property editors
Finally the property editor is registered using RegisterPropertyEditor. RegisterPropertyEditor takes four parameters:

PropertyType: PTypeInfo
Requires a pointer to a TTypeInfo record. This sounds much more complicated than it really is, all we need to do is add TypInfo to our uses clause, and use the TypeInfo function to retrieve the pointer for us. TypeInfo(SomeVariableType)

ComponentClass: TClass
This is the base class that this editor should apply to. The editor will apply to this class and any classes that descend from it. If nil is specified, this editor will apply to any class.

const PropertyName: String
If this editor should only apply to a specific property then the name of the property should be specified here. If the editor should apply to any property of the type specified in PropertyType this value should be ''.

EditorClass: TPropertyEditorClass
This is the class that has been created to deal with the property. In the above example the class is THintProperty.

Using RegisterPropertyEditor incorrectly
It is important when using RegisterPropertyEditor that you supply the correct information. Supplying the incorrect information could mean either that your editor affects incorrect properties (eg All string properties) or incorrect components.

At the other extreme, setting the parameters incorrectly could mean that only a specific property in a specific component (and descendants) is associated with your editor. This does not seem like much of a problem at first, but descendant components may wish to implement additional properties of the same type. As these properties will obviously have a different name they will not have the correct property editor assigned to them.

An example of badly registered editor already exists within the VCL. The standard editor for TCollection was registered for all classes descended from TComponent. The problem is that the lowest class capable of being displayed in the object inspector is TPersistent (the class that TComponent descends from).

If a component has a property of type TPersistent (which by default exposes its sub-properties in an expandable list), and one of its properties is of type TCollection, the result is a […] button in the object inspector that does nothing when clicked (as we saw in part two of this article series).

The solution to this problem seems quite simple. Rather than our sub-property being descended from TPersistent we could descend it from TComponent instead. However, the default behaviour for a property of type TComponent (As determined by the property editor TComponentProperty editor) is to show a list of other components, rather than the sub-properties of an embedded component.

The actual solution really is simple, but only if you know how to write a property editor.

Step 1:

type
  TExpandingRecord = class(TPersistent)

Should be changed to read

type
  TExpandingRecord = class(TComponent)  

Step 2: Create a property editor like so

type
  TExpandingRecordProperty = class(TClassProperty)
  public
    function GetAttributes : TPropertyAttributes; override;
  end;

procedure Register;

implementation

procedure Register;
begin
  RegisterComponents('Article', [TExpandingComponent]);
  RegisterPropertyEditor(TypeInfo(TExpandingRecord),
    nil, '', TExpandingRecordProperty);
end;

{ TExpandingRecordProperty }

function TExpandingRecordProperty.GetAttributes: TPropertyAttributes;
begin
  Result := [paReadOnly, paSubProperties];
end;

Step 3: Remove the RegisterComponents call from the component unit, and register it within the editor unit instead. This way we can ensure the component will not be registered without the component.

Now our property of type TExpandingRecord will show as an expanding property (due to us returning paSubProperties from GetAttributes), and the default editor for TCollection will work as the owner of the TCollection property is a TComponent.

Next page > Dialog Editors > Page 1, 2, 3, 4

All graphics (if any) in this feature created by Peter Morris.

 More Delphi
· Learn another routine every day - RTL Quick Reference.
· Download free source code applications and components.
· Talk about Delphi Programming, real time.
· Link to the Delphi Programming site from your Web pages.
· Tutorials, articles, tech. tips by date: 2001|2000|1999|1998 or by TOPIC.
· NEXT ARTICLE: Lookup! - DB/15.
Chapter fifteen of the free Delphi Database Course for beginners. See how to use lookup fields in Delphi to achieve faster, better and safer data editing. Also, find how to create a new field for a dataset and discuss some of the key lookup properties. Plus, take a look at how to place a combo box inside a DBGrid.
 Stay informed with all new and interesting things about Delphi (for free).
Subscribe to the Newsletter
Name
Email

 Got some code to share? Got a question? Need some help?
Explore Delphi Programming
About.com Special Features

Stay connected and entertained with reviews on tips on the latest HDTVs, cellphones and more. More >

Easy ways to connect two computers for networking purposes. More >

  1. Home
  2. Computing & Technology
  3. Delphi Programming

©2009 About.com, a part of The New York Times Company.

All rights reserved.