Skip over navigation

How to set a component's default event handler

Why this article?

When you double click most Delphi components at design time the IDE automatically creates an empty event handler for the default event. Sometimes you need a different event to be used as the default. The purpose of this article is to explain how to do this.

Let's make this a little more concrete by considering these three components:

  1type
  2  TCompA = class(TComponent)
  3  private
  4    fOnFoo: TNotifyEvent;
  5    fOnBar: TNotifyEvent;
  6    fOnClick: TNotifyEvent;
  7    fOnChange: TNotifyEvent;
  8    fOnChanged: TNotifyEvent;
  9    fOnCreate: TNotifyEvent;
 10  published
 11    property OnFoo: TNotifyEvent read fOnFoo write fOnFoo;
 12    property OnBar: TNotifyEvent read fOnBar write fOnBar;
 13    property OnChange: TNotifyEvent read fOnChange write fOnChange;
 14    property OnChanged: TNotifyEvent read fOnChanged write fOnChanged;
 15    property OnClick: TNotifyEvent read fOnClick write fOnClick;
 16    property OnCreate: TNotifyEvent read fOnCreate write fOnCreate;
 17  end;
 18
 19  TCompB = class(TComponent)
 20  private
 21    fOnFoo: TNotifyEvent;
 22    fOnBar: TNotifyEvent;
 23    fOnChange: TNotifyEvent;
 24  published
 25    property OnFoo: TNotifyEvent read fOnFoo write fOnFoo;
 26    property OnBar: TNotifyEvent read fOnBar write fOnBar;
 27    property OnChange: TNotifyEvent read fOnChange write fOnChange;
 28  end;
 29
 30  TCompC = class(TComponent)
 31  private
 32    fOnFoo: TNotifyEvent;
 33    fOnBar: TNotifyEvent;
 34  published
 35    property OnFoo: TNotifyEvent read fOnFoo write fOnFoo;
 36    property OnBar: TNotifyEvent read fOnBar write fOnBar;
 37  end;
Listing 1

Double clicking the components in the Delphi IDE creates the following event handlers:

  • TCompA.OnCreate
  • TCompB.OnChange
  • TCompC.OnBar

Suppose we want the OnFoo event to be created in all cases? We'll find out how to do this in the course of this article.

Some background

Before we can solve our problem we need to examine how Delphi 7 decides which event to use as the default. (Things are a subtly different in Delphi 4 – as we'll see later).

When a component is double clicked, Delphi calls the Edit method of the registered component editor. The default component editor is TDefaultEditor and it is this editor that is responsible for creating the event handler. Let's examine how TDefaultEditor.Edit decides on the the default event.

Through a round about process, the Edit method ultimately calls the TDefaultEditor.EditProperty method once for each of the component's properties. Rather than pass a reference to the property itself to EditProperty, it is a reference to the associated property editor that is actually passed. EditProperty checks the name of each event property and records the property editor of the preferred event. These events, in order of preference, are:

  • OnCreate
  • OnChange
  • OnChanged
  • OnClick
  • the first event alphabetically

This means that if OnCreate is present it is used as the default. If it is not present then OnChange is used, and so on. If none of the listed events is present then the event that is first alphabetically is used.

Overriding the default behaviour

From the discussion above it is clear that if we are to specify the default event ourselves we need to change the way that TDefaultEditor.EditProperty works. Luckily for us this method is virtual, so we can subclass TDefaultEditor and override EditProperty. The new class can be declared very simply as follows:

  1type
  2  TMyCompEditor = class(TDefaultEditor)
  3  protected
  4    procedure EditProperty(const PropertyEditor: IProperty;
  5      var Continue: Boolean); override;
  6  end;
Listing 2

The implementation is equally straightforward:

  1procedure TMyCompEditor.EditProperty(const PropertyEditor: IProperty;
  2  var Continue: Boolean);
  3begin
  4  // only call inherited method if required event name found
  5  if CompareText(PropertyEditor.GetName, 'OnFoo') = 0 then
  6    inherited;
  7end;
Listing 3

Let's look at how this works. Recall that, in the base class, TDefaultEditor.EditProperty was called once for each property in the component. The method chose the default event from all those it was passed. Now, if TDefaultEditor.EditProperty was only called once then the default event must be the one whose property editor was passed in that single call.

Our overridden method works by ensuring that the inherited method is only called once – for the event we want to be the default. In our descendant class, it is TMyCompEditor.EditProperty that is called for each property in the component. The method simply checks for a property editor whose property has the required name ('OnFoo') and passes that property editor – and only that property editor – to the inherited method. And violá – OnFoo is the default event!

A reusable solution

In the code we developed in the previous section we hard-wired the name of the default event.

Our next job is to generalize the solution to make the code easier to re-use. Since the classes are distinguished only by the name of the default event they select, we will develop a base class that has an abstract method that descendants override to return the name of the required event.

Here is the declaration of the base class:

  1type
  2  TCompEditorBase = class(TDefaultEditor)
  3  protected
  4    function DefaultEventName: string; virtual; abstract;
  5      { override to return name of default event }
  6    procedure EditProperty(const PropertyEditor: IProperty;
  7      var Continue: Boolean); override;
  8      { records property editor of default event }
  9  end;
Listing 4

The implementation should come as no surprise – we simply replace the hard-wired name in the earlier class with a call to the abstract method:

  1procedure TCompEditorBase.EditProperty(const PropertyEditor: IProperty;
  2  var Continue: Boolean);
  3begin
  4  if CompareText(PropertyEditor.GetName, DefaultEventName) = 0 then
  5    inherited;
  6end;
Listing 5

We can now implement a component editor for a specific default event – our old friend OnFoo once more – simply by overriding the abstract DefaultEventName method as follows:

  1type
  2  TCompEditor = class(TCompEditorBase)
  3  protected
  4    function DefaultEventName: string; override;
  5  end;
  6  
  7// ...
  8  
  9function TCompEditor.DefaultEventName: string;
 10begin
 11  Result := 'OnFoo';
 12end;
Listing 6

Tidying up

To get these examples to compile we need to use the Classes, SysUtils, DesignIntf and DesignEditors units. Note that the last two units can only be used when integrating into the IDE – they can't be used in stand-alone applications.

We also need to register the component editor in our unit's Register procedure as follows:

  1procedure Register;
  2begin
  3  RegisterComponentEditor(TCompA, TCompEditor);
  4  // etc ...
  5end;
Listing 7

Delphi 4 differences

In Delphi 4 TDefaultEditor.EditProperty has a different signature. It is defined as:

  1procedure EditProperty(PropertyEditor: TPropertyEditor;
  2  var Continue, FreeEditor: Boolean);
Listing 8

We can deal with this using conditional compilation. Assuming the DELPHI6ANDUP symbol is defined when we are using Delphi 6 and above, we can implement TCompEditorBase.EditProperty as follows:

  1procedure TCompEditorBase.EditProperty(
  2  {$IFDEF DELPHI6ANDUP}
  3  const PropertyEditor: IProperty; var Continue: Boolean
  4  {$ELSE}
  5  PropertyEditor: TPropertyEditor; var Continue, FreeEditor: Boolean
  6  {$ENDIF}
  7);
  8begin
  9  if CompareText(PropertyEditor.GetName, DefaultEventName) = 0 then
 10    inherited;
 11end;
Listing 9

Additionally, we need to replace the DesignIntf and DesignEditors units with DsgnIntf:

  1uses
  2  Classes, SysUtils,
  3  {$IFDEF DELPHI6ANDUP}
  4  DesignIntf, DesignEditors;
  5  {$ELSE}
  6  DsgnIntf;
  7  {$ENDIF}
Listing 10

Summary

In this article we have reviewed how to change the event handler that is opened in the IDE when a component is double clicked.

We first examined how Delphi decides which events to use for this purpose and then developed a sub class of TDefaultEditor that can explicitly specify the default event. We then went on to generalize the solution by developing an abstract base class for component editors that change the default event.

The main discussion closed with a look at how to register the component editor.

Finally we discussed the changes that need to be made to compile the code under Delphi 4.

Demo code

A demo program to accompany this article can be found in the delphidabbler/article-demos Git repository on GitHub.

You can view the code in the article-17 sub-directory. Alternatively download a zip file containing all the demos by going to the repository's landing page and clicking the Clone or download button and selecting Download ZIP.

See the demo's README.md file for details.

This source code is merely a proof of concept and is intended only to illustrate this article. It is not designed for use in its current form in finished applications. The code is provided on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or implied.

The demo is open source. See the demo's LICENSE.md file for licensing details.

Feedback

I hope you found this article useful.

If you have any observations, comments, or have found any errors there are two places you can report them.

  1. For anything to do with the article content, but not the downloadable demo code, please use this website's Issues page on GitHub. Make sure you mention that the issue relates to "article #17".
  2. For bugs in the demo code see the article-demo project's README.md file for details of how to report them.