Writing Property Editors

Writing Property Editors

Writing components is an effective way to customize Delphi, helping developers to build applications more quickly without requiring a detailed knowledge of low-level techniques. The Delphi environment is also open to extensions. In particular, you can extend the Object Inspector by writing custom property editors and the Form Designer by adding component editors.

Along with these techniques, Delphi offers internal interfaces to add-on tool developers. Using these interfaces, known as the OpenTools API, requires an advanced understanding of how the Delphi environment works and a fairly good knowledge of many advanced techniques that are not discussed in this book. For references to technical information and some examples of these techniques, see Appendix A, "Extra Delphi Tools by the Author."


The OpenTools API in Delphi has changed considerably over time. For example, the DsgnIntf unit from Delphi 5 has been split into DesignIntf, DesignEditors, and other specific units. Borland has also introduced interfaces to define the sets of methods for each kind of editor. However, most of the simpler examples, such as those presented in this book, compile almost unchanged from earlier versions of Delphi. For more information, you can study the extensive source code in Delphi's \Source\ToolsApi directory. Notice also that with Delphi 6 Update Pack 2 Borland has for the first time shipped a Help file with the documentation of the OpenTools API.

Every property editor must inherit from the abstract TPropertyEditor class, which is defined in the DesignEditors unit and provides a standard implementation for the IProperty interface. Delphi already defines some specific property editors for strings (the TStringProperty class), integers (the TIntegerProperty class), characters (the TCharProperty class), enumerations (the TEnumProperty class), and sets (the TSetProperty class), so you can inherit your property editor from the one for the property type you are working with.

In any custom property editor, you must redefine the GetAttributes function so it returns a set of values indicating the capabilities of the editor. The most important attributes are paValueList and paDialog. The paValueList attribute indicates that the Object Inspector will show a combo box with a list of values (eventually sorted if the paSortList attribute is set) provided by overriding the GetValues method. The paDialog attribute style activates an ellipsis button in the Object Inspector, which executes the editor's Edit method.

An Editor for the Sound Properties

The sound button you built earlier has two sound-related properties: SoundUp and SoundDown. These are strings, so you can display them in the Object Inspector using a default property editor. However, requiring the user to type the name of a system sound or an external file is not friendly, and it's a bit error-prone.

We could write a generic editor to handle filenames, but you want to be able to choose the name of a system sound as well. (System sounds are predefined names of sounds connected with user operations, associated with actual sound files in the Windows Control Panel's Sounds applet.) For this reason, I built a more complex property editor. My editor for sound strings allows a user to either choose a value from a drop-down list or display a dialog box from which to load and test a sound (from a sound file or a system sound). The property editor provides both Edit and GetValues methods:

  TSoundProperty = class (TStringProperty)
    function GetAttributes: TPropertyAttributes; override;
    procedure GetValues(Proc: TGetStrProc); override;
    procedure Edit; override;

The default Delphi convention is to name a property editor class with a name ending with Property and all component editors with a name ending with Editor.

The GetAttributes function combines the paValueList (for the drop-down list) and paDialog (for the custom edit box) attributes, and also sorts the lists and allows the selection of the property for multiple components:

function TSoundProperty.GetAttributes: TPropertyAttributes;
  // editor, sorted list, multiple selection
  Result := [paDialog, paMultiSelect, paValueList, paSortList];

The GetValues method calls the procedure it receives as a parameter many times, once for each string it wants to add to the drop-down list (as you can see in Figure 9.12):

procedure TSoundProperty.GetValues(Proc: TGetStrProc);
  // provide a list of system sounds
  Proc ('Maximize');
  Proc ('Minimize');
  Proc ('MenuCommand');
  Proc ('MenuPopup');
  Proc ('RestoreDown');
Figure 9.12: The list of sounds provides a hint for the user, who can also type in the property value or double-click to activate the editor (shown later, in Figure 9.13).

A better approach would be to extract these values from the Windows Registry, where all these names are listed. The Edit method is straightforward: It creates and displays a dialog box. I could have displayed the Open dialog box directly, but I decided to add an intermediate step to allow the user to test the sound. This is similar to what Delphi does with graphic properties: You open the preview first, and load the file only after you've confirmed that it's correct. The most important step is to load the file and test it before you apply it to the property. Here is the code for the Edit method:

procedure TSoundProperty.Edit;
  SoundForm := TSoundForm.Create (Application);
    SoundForm.ComboBox1.Text := GetValue;
    // show the dialog box
    if SoundForm.ShowModal = mrOK then
      SetValue (SoundForm.ComboBox1.Text);

The GetValue and SetValue methods are defined by the base class, the string property editor. They read and write the value of the current component's property that you are editing.

As an alternative, you can access the component you're editing by using the GetComponent method (which requires a parameter indicating which of the selected components you are working on—0 indicates the first component). When you access the component directly, you also need to call the Designer object's Modified method (a property of the base class property editor). You don't need this Modified call in the example, because the base class SetValue method does this automatically for you.

The previous Edit method displays a dialog box—a standard Delphi form that is built visually, as always, and added to the package hosting the design-time components. The form is quite simple; a ComboBox displays the values returned by the GetValues method, and four buttons allow you to open a file, test the sound, and terminate the dialog box by accepting the values or canceling. You can see an example of the dialog box in Figure 9.13. Providing a drop-down list of values and a dialog box for editing a property causes the Object Inspector to display only the arrow button that indicates a drop-down list and to omit the ellipsis button to indicate that a dialog box editor is available. In this case, as it happened for the default Color property editor, the dialog box is obtained by double-clicking the current value or pressing Ctrl+Enter.

Click To expand
Figure 9.13:  The Sound Property Editor's form displays a list of available sounds and lets you load a file and hear the selected sound.

The form's first two buttons have a method assigned to their OnClick event:

procedure TSoundForm.btnLoadClick(Sender: TObject);
  if OpenDialog1.Execute then
    ComboBox1.Text := OpenDialog1.FileName;
procedure TSoundForm.btnPlayClick(Sender: TObject);
  PlaySound (PChar (ComboBox1.Text), 0, snd_Async);

Notice that it is far from simple to determine whether a sound is properly defined and is available. (You can check the file, but the system sounds create a few issues.) The PlaySound function returns an error code when played synchronously, but only if it can't find the default system sound it attempts to play if it can't find the sound you ask for. If the requested sound is not available, it plays the default system sound and doesn't return the error code. PlaySound looks for the sound in the Registry first and, if it doesn't find the sound there, checks to see whether the specified sound file exists.


If you want to further extend this example, you might add graphics to the drop-down list displayed in the Object Inspector—if you can decide which graphics to attach to particular sounds.

Installing the Property Editor

After you've written this code, you can install the component and its property editor in Delphi. To accomplish this, you have to add the following statement to the unit's Register procedure:

procedure Register;
  RegisterPropertyEditor (TypeInfo(string), TMdSoundButton, 'SoundUp',
  RegisterPropertyEditor (TypeInfo(string), TMdSoundButton, 'SoundDown',

This call registers the editor specified in the last parameter for use with properties of type string (the first parameter), but only for a specific component and for a property with a specific name. These last two values can be omitted to provide more general editors. Registering this editor allows the Object Inspector to show a list of values and the dialog box called by the Edit method.

To install this component, you can add its source code file to an existing or new package. Instead of adding this unit and the others in this chapter to the MdPack package, I created a second package containing all the add-ins built in this chapter. The package is named MdDesPk (Mastering Delphi design package). What's new about this package is that I compiled it using the {$DESIGNONLY} compiler directive. This directive is used to mark packages that interact with the Delphi environment, installing components and editors, but are not required at run time by applications you've built.


The source code for all the add-on tools is in the MdDesPk subdirectory, along with the code for the package used to install them. There are no examples demonstrating how to use these design-time tools, because all you have to do is select the corresponding components in the Delphi environment and see how they behave.

The property editor's unit uses the SoundB unit, which defines the TMdSoundButton component. For this reason, the new package should refer to the existing package. Here is its initial code (I'll add other units to it later in this chapter):

package MdDesPk;
{$R *.RES}
{$DESCRIPTION 'Mastering Delphi DesignTime Package'}
  PeSound in 'PeSound.pas',
  PeFSound in 'PeFSound.pas' {SoundForm};

Part I: Foundations