Gray Paper - Delphi Property Editors for Beginners
Updated with corrections
Copyright © 1995, Greg Truesdell
CIS: 74131,2175
September 10, 1995

Warning!
The information found in this document is an account of the author's research into property editors in Delphi.  I am not directly associated with Borland, nor have I any covert knowledge beyond the content of the manuals and the extent of my own experimentation.  Wow, what a disclaimer, eh?  Probably just an attention grabber.

Introduction

One of the more interesting aspects of Borland's Delphi®1 is the relative ease with which one can create and customize components in the visual environment.  Whether or not the component is actually visible at run time, many of the component's properties and methods can be interacted with during development.  It is this interaction (via the Object Inspector) that makes the process of development so much more friendly.

When a developer decides that a component is the best vehicle for a program requirement, (s)he must also consider how the component is presented within the development environment.  Delphi provides many standard property editors for the bulk of data types available in Object Pascal.  However, there is no generic way for Delphi to know how to edit custom data types or objects.  Borland has provided a way for developers (through the DsgnInfc unit) to register property editors customized for programmer-defined data types.

Although Borland is usually fairly good at providing documentation on all aspects of their compilers, the rush to release Delphi (thanks, by the way) precluded their production of a complete and user-friendly guide to creating custom Property Editors.  They have, of course, offered to sell you the source code for the Visual Component Library, which contains many examples.  It may seem strange, but I have not opted to obtain the source code.  I will, of course, but the price is as much as the whole desktop package.  Some of you may not be able to afford it either, so with a little prompting from my friends, I have decided to pass on to you what I have learned.  The scope of this paper has been limited to property editors using custom dialog windows and programmer defined data types.

Questions, Questions ... but Few Answers.

How do Property Editors work? Well, that was my first question, anyway.  Obviously, when you select the [...] button next to a property of type TString, you see a dialog appear containing an editor.  You type in lines of text and ... viola ... the property is set.  So what goes on behind the scenes?  How did Delphi know to display the editor dialog?  How did the text entered into the editor get into the property?  And how does the text get stored with the form so it is reloaded when the form is loaded?

It is important to answer these questions or you will not be successful at creating custom property editors for yourself.  Unfortunately, the questions aren't answered very well in the documentation.  So let me have a crack at how I have answered these questions for myself.  Just one caveat: I am guessing. Well, a bit, anyway.

First, consider the following assumptions one can make about property editors:

1. The Delphi design-mode must know that the editor exists, and what data type it is designed to handle.
2. The property editor must exist, and must be designed to know how to edit the data type.
3. The property editor must have access to the property that is being edited.
4. The property editor probably has a number of special attributes that determine how it is invoked.
5. There are probably certain things about property editors that no one is willing to tell us. (... at least not without forking over some cash.)

Let's assume that we have a custom data type we want to edit.  The declaration for this data type may be as follows:

Type TMyDataType = Record
   Name : String;
   Amount : Real;
   Items : Integer;
   Paid : Boolean;
  End;

In our component object, a property exists of this data type.  Say ...

MyComponent = Class(TComponent)
Private
 FRecord : TMyDataType;
Published
 Property TheRecord : TMyDataType Read FRecord Write FRecord;
End;

Normally, of course, if the data type was one that Delphi had a property editor for it would have no trouble handling the property.  However, in this case, the data type TMyDataType is unknown.  There is no editor available to edit the property.  What is even worse, is that Delphi will not allow you to publish the property.  Record data types are not supportable.  You must create an object from your data type.  Here is how you might implement your Record as a Property:

Type
 TMyDataType = Record
  Name : String;
  Amount : Real;
  Items : Integer;
  Paid : Boolean;
 End;

 TMyDataObject = Class(TObject)
 Private
  MyData : TMyDataType;
 Public
  Constructor Create( Name : String );
  Destructor Free;
  Procedure SetRecord( var Rec : TMyDataType );
  Procedure GetRecord( var Rec : TMyDataType );
 End;

Now the data type is an object.  After you have written the Constructor, Destructor and the access methods SetRecord and GetRecord, you can move on to the next step.  Delphi will now accept the following component (simplified, of course):

MyComponent = Class(TComponent)
 Private
  FRecord : TMyDataObject;
 Published
  Property TheRecord : TMyDataType Read FRecord Write FRecord;
End;

Now you might think that you could just replace the "Write FRecord" clause of the Property statement with something like SetFRecord.  Well, you certainly could but this would not represent a property editor.  No matter how hard you try to load a form with your special editor you will be unsuccessful.  This is not how property editors work.  First you must create a dialog form capable of editing and/or displaying your new data type.  Then you need to create the property editor object that will open the dialog form and interact with the data.
 

Creating the Property Editor Dialog

There are two major steps:

1. Create a Dialog Form that can create and edit your data type.
2. Create a descendant of  TPropertyEditor  that can interact with your Dialog Form.

So, for step 1 you just create a new form and program the actions required for the form to handle the fields in your record.  It can be simple with labels and edit boxes; or it can be complex with all types of data validation, etc.  Make it pretty or make it ugly ... but you gotta make it.  Let assume that you have created a form and called it TMyDataForm.  Lets also assume that the (partial) declaration for this form is:

Type
  TMyDataForm = Class(TForm)
    Label1: TLabel;
    Label2: TLabel;
    Label3: TLabel;
    Label4: TLabel;
    strName: TEdit;
    strAmount: TEdit;
    strItems: TEdit;
    chkPaid: TCheckBox;
        btnOK: TButton;
        btnCancel: TButton;
    Procedure FormCreate(Sender: TObject);
  Private
    { Private declarations }
  Public
    { Public declarations }
    Function GetName : String;
    Function GetAmount : Real;
    Function GetItems : Integer;
    Function GetPaid : Boolean;
  End;

The idea here is that the form will accept the Name, Amount, Items and Paid field data, then accept the values entered after pressing the btnOK button. The public functions GetName, GetAmount, GetItems and GetPaid return the appropriate data type for each field by translating (where necessary) the string version of the data.

Now assume you have the editor dialog form ready for action.  How do you turn it into a Property Editor?  You need to create a descendant of TPropertyEditor that knows how to display the form and obtain the data.

Creating the Property Editor Object

In the same file as your form, create an entry for the property editor object:

  TMyDataEditor = Class(TPropertyEditor)
    Function  GetAttributes : TPropertyAttributes;  Override;
    Function  GetValue      : String;               Override;
    Procedure Edit;                                 Override;
  end;

The reasoning here is to create a descendant of TPropertyEditor that overrides the default methods you must customize for your editor.  The method GetAttributes is required to return a set of property attributes for this property editor.  Let's just settle for the standard attributes for a dialog:

Function  TMyDataEditor.GetAttributes : TPropertyAttributes;
begin
  Result := inherited GetAttributes +
     [paDialog] -[paSubProperties];
end;

Note the use of inherited.  It is used to obtain the attributes inherited from TPropertyEditor.  Furthermore, I have added the paDialog attribute because this editor is a Dialog.  There are a number of attributes to consider, and you can find a list of attributes later in this text.

When the property is displayed in the Object Inspector, it calls the GetValue method to obtain a string representation of the contents of the data.  You can put anything that seems reasonable here, but it is likely with records you will only want to show the data type.  To instruct Delphi to display the data type (TStrings data types are displayed as (TStrings)), try the following code in GetValue:

Function  TMyDataEditor.GetValue : String;
begin
 Result := '(TMyDataObject)';
end;

Of course, you can also use a more generic method that looks up the property name by dereferencing the GetPropType record pointer.  GetPropType is a pointer to a record of information about the data type being edited.  Try the following code, since it will always display the correct type name:

Function  TMyDataEditor.GetValue : String;
begin
  FmtStr(Result, '(%s)', [ GetPropType^.Name ]);
end;

Yet again, you might decide that you would rather see the Name field as a representative of the record.  If so, you will need to locate the contents of the Name field in your data structure object.  To do this, you need to get a reference to the TMyDataObject object being edited. It would be rather stupid if you could not do this, so Delphi defines the GetOrdValue identifier.  You can use this to locate the data, and access it.  You will need to typecast this reference, since GetOrdValue is a generic pointer.  In this case you might try:

Function  TMyDataEditor.GetValue : String;
begin
  Result := TMyDataObject(GetOrdValue).GetName;
end;

Now the contents of the Name field will be displayed in the Object Inspector.

The Property Editor's Edit Method

Now you must program the Edit method.  This is where Delphi will go when it needs to edit any property of type TMyDataObject.  The Edit method is a procedure.  Since it does not return anything, your code must handle it internally.  Remember that the GetValue method is used to return a string that represents the contents of the property.  Delphi will call it whenever it needs to display the property in the Object Inspector.  Now, I will give you an example of how you might implement an editor for the TMyDataObject data type.  Please not that the code has been simplified for clarity.  Normally you would want to trap errors.

Procedure TMyDataEditor.Edit;
var
  MyData : TMyDataObject;  { reference to object being edited }
  MyRecord : TMyDataType;  { temp holding record }
Begin
  { create the form }
  with TMyDataForm.Create( Application ) do begin
    { if the OK button was pressed }
    if ShowModal = mrOK then begin
      { fill the temporary record }
      with MyRecord do begin
        Name := GetName;
        Amount := GetAmount;
        Items := GetItems;
        Paid := GetPaid;
      end;
      { set the record in the object being edited }
      MyData := TMyDataObject(GetOrdValue);
      MyData.SetRecord( MyRecord );
    end;
    { free the form }
    Free;
  end;
End;

So, the edit method creates the Dialog Form, shows it modal, fills the temporary record with the edited fields then uses the SetRecord method of  the TMyDataObject being edited to set the object's record.  That's it.  The property is edited.

Registering the Editor

That was a fair amount of work, wasn't it.  Yet it's not so bad once you get through it.  The last very important step is to register the newly created property editor.  It is probably best to include the registration code in the Register procedure provided by the component you are writing.  Assuming that this is what you want to do, place the following code into the Register procedure of your component:

RegisterPropertyEditor( TypeInfo(TMyDataObject), Nil,'',
  TMyDataEditor );

This instructs Delphi to register our editor TMyDataEditor as the editor for properties of type TMyDataObject when the component is loaded.  Assuming all went well .... That's it.

Some Details

Of course, there are always details.  Consider the following list of things to remember:

1. Ensure that your dialog form returns mrOK when the OK button is pressed.
2. Remember to include DsgnIntf in the Uses statement of your editor dialog form source file.
3. Keep the Editor form and the TPropertyEditor code in the same file.
4. Put the RegisterPropertyEditor statement in the component code file's Register procedure.
5. Include plenty of Try..Finally statements in your code.  Errors can be hard to find.

You have seen a few specific items used to access information by your property editor.  They are:

GetPropType
This method returns a pointer to a record of type information available at run time. The following PTypeInfo definition is defined:

Type
  PTypeInfo = ^TTypeInfo;
  TTypeInfo = record
    Kind: TTypeKind;
    Name: string;
    TypeData: TTypeData;
  End;

In our example, I only used the Name field to retrieve the type's proper name.

GetOrdValue
Actually, this is one of  four methods provided for use in the GetValue method.  Since our data type is ordinal, we will use GetOrdValue.  However, GetFloatValue, GetStringValue and GetMethodValue are provided (see Component Writer's Help).

Inherited GetAttributes
I used this inherited method to obtain the default attributes of the TPropertyEditor object.  As you saw, I used paDialog, paReadOnly and paSubProperties, as this indicates that the property can use a dialog to edit the entire property, it's read-only in the Object Inspector and there are no Sub-Properties to edit..  That's true, in our case.  The complete list of options is:

paAutoUpdate
Calls the SetValue method after each change in the value, rather than after the entire value is approved.

paDialog
The editor can display a dialog box for editing the entire property. The Edit method opens the dialog box.

paReadOnly
The property is read-only in the Object Inspector. This is used for sets and fonts, which the user cannot type directly.

paMultiSelect
The property should appear when the user selects multiple components. Most property editors allow multiple selection. The notable exception is the editor for the Name property.

paSortList
The Object Inspector should sort the list of values alphabetically.

paSubProperties
The property is an object that has subproperties that can display. The GetProperties method handles the subproperty lists.

paValueList
The editor can give a list of enumerated values. The GetValues method builds the list.
 

Oh, and read the Component Writer's Guide2 and Help as though it really means something.  Read the manual first, then look at the help topics in the Help file.  They can be confusing, but what isn't.  Try the code in this document.  No, it is not complete. There is, however, enough for you to get started.  There is no cerebral thrill greater than the thrill of a programmer who has just solved a confusing problem.3

In Closing

This paper has in no way exhausted the subject of Property Editors.  In fact, it has only scraped the surface of what you can do.  In the case of About dialog boxes, you can create property editors that do nothing but display dialogs.  Great for registration dialogs, too.  Well, maybe.

I originally wrote this paper as a guide for myself as I uncovered more about property editors.  Since then, on the behest of some colleges, I have rewritten it to be a bit more general and possibly instructive.  I can not vouch for the accuracy of anything written here, except to say ... It Works for Me.
 

About the Author

You'll probably want to skip this.

The author is the Chief Architect, 'Keeper of the Model' and Principal Developer for the Data Warehouse project at BC Tel (what a mouthful).  He lives in beautiful British Columbia, Canada (Hollywood of the North) with his Wife Julia and Dog Buster.  Fortunately, both children are grown and living on their own.

References and Notes

___________________________
1 Delphi is Copyright © 1995 by Borland International
2 I think the word 'guide' as used here should be taken with a grain of salt.  Perhaps the word 'overview' is more appropriate.

3 'Programming and the Eureka Experience' by Greg Truesdell