1. Technology
RTL reference|Glossary|Tips/Tricks|FREE App/VCL|Best'O'Net|Books|Link To
 
Creating Custom Delphi Components, Part III
Page 2: The component editor; Adding to third party editors
 More of this Feature
• Page 1: Custom Editors
• Page 3: Property 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 component editor
Component editors are triggered when the developer right-clicks a component at design time, just before the context menu appears Delphi searches for a component editor associated with the component. If an editor is found Delphi executes the methods of the component editor to allow it to add its own menu items.

Although it is possible to write property editors with simpler functionality than a component editor, the component editor still remains the easiest of the editors to understand as there are really only five methods and two properties to become familiar with:

procedure Edit; virtual;
function GetVerbCount: Integer; virtual;
function GetVerb
  (Index: Integer): string; virtual;
procedure ExecuteVerb(Index: Integer); virtual;
procedure PrepareItem
  (Index: Integer; const AItem: TMenuItem); virtual;

property Component: TComponent read FComponent;
property Designer: IFormDesigner read GetDesigner;

Edit
Is called by Delphi whenever the user double clicks on the component. Many components use this method to create an OnClick event, whereas TForm uses this event to create an OnCreate event. It is possible to override this standard functionality and tell the IDE to do whatever we want it to (eg, execute one of our additional verbs).

GetVerbCount
Is called by Delphi when it needs to know how many items we wish to add to the context menu.

GetVerb
Is called once per menu item. If we specify that we wish to add four items, it will be called four times (with the values 0,1,2,3). The purpose of this method is to inform the IDE of the captions to appear in the context menu.

case Index of
  0: Result := 'Item 0';
  1: Result := 'Item 1';
end;

ExecuteVerb
Is called when the developer selects one of the custom menu items in the context menu.

PrepareItem
Is called once for each item we add. Immediately after Delphi has finished creating our menu item it calls this method, so that we may alter it as we wish. It is possible to hide / disable the item, or even add sub items.

Note: When using PrepareItem be aware that you should not free the menu item. If you do not wish it to be displayed you should set Visible to false. Also, be sure to add Menus to your uses clause.

Component
This property should be used in order to gain access to the component that was right clicked. You can simply typecast it to the correct class type.

Designer
This is the designer interface for Delphi. It is capable of performing many tasks, most of which are not within the scope of this article, but it is used in these demonstrations to inform the IDE that we have changed something. As a result Delphi will update the properties in the object inspector, and flag the project as modified. It is demonstrated in greater detail later in this article. Once we have overridden the relevant methods, all that is left to do is to register it, this is done like so

procedure Register;
begin
  RegisterComponents( [ComponentClass], 'Tab name');
  RegisterComponentEditor( ComponentClass, ComponentEditor);
end;

The following example will show you how to add two items to the context menu of a TTable (Open and Close). Our TTableEditor will descend from TComponentEditor, which is the base class for all component editors.

type
  TTableEditor = class(TComponentEditor)
  public
    { Public declarations }
    procedure Edit; override;
    procedure TTableEditor.ExecuteVerb(Index: Integer);
    function GetVerb(Index: Integer): string; override;
    function GetVerbCount: Integer; override;
    procedure PrepareItem
    (Index: Integer; const AItem: TMenuItem); override;
  end;

procedure TTableEditor.Edit;
begin
  with TTable(Component) do
    if Active then
      Close
    else
      Open;
end;

procedure TTableEditor.ExecuteVerb(Index: Integer);
begin
  case Index of
    0: TTable(Component).Open;
    1: TTable(Component).Close;
  end;
  //Tell the IDE something changed
  Designer.Modified;
end;

function TTableEditor.GetVerb(Index: Integer): string;
begin
  case Index of
    0 : Result := 'Open';
    1 : Result := 'Close';
  end;
end;

function TTableEditor.GetVerbCount: Integer;
begin
  Result := 2;
end;

procedure TTableEditor.PrepareItem
  (Index: Integer; const AItem: TMenuItem);
begin
  case Index of
    0: AItem.Enabled := not TTable(Component).Active;
    1: AItem.Enabled := TTable(Component).Active;
  end;
  //Tell the IDE something changed
  Designer.Modified;
end;

   Adding additional items
If you were to compile the above example, and install it, whenever you clicked on a TTable you would see two additional menu items "Open" and "Close". When I say "additional" I mean only additional to the standard TComponent context menu. When you compare our context menu with the context menu you would normally see when you right click a TDataSet you may notice there are actually some menu items missing too.

Note: Registering property / component editors is covered later in this article

   Order of preference
When writing this article I tried to find a component that already had a custom editor assigned to it in order to demonstrate the above problem clearly. I decided to write the above component editor based on TTable, although I originally intended to write it based on TDataSet so that it would work with both TTable and TQuery. The problem is that both TTable and TQuery already have their own custom component editors, which would stop an editor registered on TDataSet from being invoked. This is how it works

1. Developer right-clicks the component.
2. Delphi gets the class of the component clicked.
3. Delphi checks if a custom editor has been registered for this class.
4. If not, Delphi checks the parent class until an editor is found.
5. If an editor is found, an instance is created, and it is executed.

As Delphi has registered component editors at a later level to TDataSet, our editor would not even get a look in. The answer at first seems quite simple, if we were to register our component editor twice (once for TTable and once for TQuery) then our component editor would be executed (Delphi will use the most recently installed editor) our editor will be executed. This is absolutely correct, that is what will happen. The problem is that now we have replaced the standard editor, so the standard functions such as

· Fields editor
· Explore
· Execute (TQuery)
· Delete table (TTable)
· Rename table (TTable)
· Update table definition (TTable)

will no longer appear. Is it acceptable to remove these options ? I would say it isn't. Then should we have to add the functionality of these options ourselves ? I personally wouldn't fancy taking on that task.

Trying to tackle this problem offers three possible solutions, unfortunately, none of these solutions work and the problem remains unsolved. Although one of these solutions is suitable for replacing component editors written by third parties which, is why this part of the article has been included.

Solution 1: When returning GetVerbCount etc, call the inherited methods and then a modified result, eg:

function TMyEditor.GetVerbCount: Integer;
begin
  //We want to add 2 additional items
  Result := inherited GetVerbCount +2;
end;

Problem: Our editor descends from TComponentEditor, which means we will be returned no verbs.

Solution 2: When our editor is created, look through the list of registered editors and find the one registered before our own. Create an instance of that class so that we can query how many verbs it has etc.

Problem: The GetComponentEditor function in DsgnIntf.pas will return our own editor, and the "ComponentClassList" it uses is declared under the implementation section of the unit so is not accessible.

Solution 3: Descend our component editor from the existing component editor, all inherited method calls will then return the correct values.

Problem: Not all versions of Delphi come with the "$(Delphi)\Source\Property Editors" directory (Standard version), and not all versions of Delphi that have this directory are able to compile the contents of it (Professional version).

These are the problems encountered when we try to interface with the standard Delphi editors, however, when installing third party components which include their own editors we are guaranteed that these editors will compile (otherwise they would not install).

Note: Please remember to uninstall this editor otherwise you wont be able to access the default editor functions of these objects.

   Adding to third party editors
Adding items to a component editor that has been registered by a third party is quite simple. All that we need to do is to descend our component editor from the third party's editor.

The following example will show you how to create a TPanelEditor which provides a quick way to clear the caption property of a TPanel. Then we will descend a new editor from this class and implement two additional features.

  TPanelEditor = class(TComponentEditor)
  public
    { Public declarations }
    procedure ExecuteVerb(Index: Integer); override;
    function GetVerb(Index: Integer): string; override;
    function GetVerbCount: Integer; override;
  end;

procedure TPanelEditor.ExecuteVerb(Index: Integer);
begin
  TPanel(Component).Caption := '';
end;

function TPanelEditor.GetVerb(Index: Integer): string;
begin
  Result := 'Clear caption';
end;

function TPanelEditor.GetVerbCount: Integer;
begin
  Result := 1;
end;

After reading through the first example, this component editor will be very simple, so it will not need explaining. If you needed to add extra commands to this component editor it would also be very simple. However, what if this component editor was written by someone else ? You could add your own commands to the editor, but the next time you receive an update from the author you would need to apply your changes again.

The simple answer is to descend a new component editor from the original and then add our own commands. Even if the original editor is updated, our component editor should need no code changes at all.

TAdvancedPanelEditor = class(TPanelEditor)
    { Public declarations }
    procedure ExecuteVerb(Index: Integer); override;
    function GetVerb(Index: Integer): string; override;
    function GetVerbCount: Integer; override;
  end;

Step 1:
The IDE needs to know how many items there are in our editor. All we need to do is ask our inherited class how many items there are, and then add the number of "new" items to that result. In the following example two items will be added.

function TAdvancedPanelEditor.GetVerbCount: Integer;
begin
  Result := inherited GetVerbCount + 2;
end;

Step 2:
Next the IDE needs to know how to label each item in the list. At this point we decide if we should return the caption of one of our new items, or the caption of one of the original items.

function TAdvancedPanelEditor.GetVerb
               (Index: Integer): string;
var
  NewIndex: Integer;
begin
  if Index < inherited GetVerbCount then
    Result := inherited GetVerb(Index)
  else begin
    NewIndex := Index - inherited GetVerbCount;
    case NewIndex of
      0: Result := 'Align client';
      1: Result := 'Align bottom';
    end;
  end;
end;

If the index is less than the verb count of our ancestor class we ask the ancestor class what the caption should be, otherwise we decide what that caption should be.

Step 3:
Finally, we should execute the appropriate code when a component editor's verb is selected. To do this we just decide if we should request the ancestor class to perform its code, or if we should execute our own.

procedure TAdvancedPanelEditor.ExecuteVerb
                 (Index: Integer);
var
  NewIndex: Integer;
begin
  if Index < inherited GetVerbCount then
    inherited ExecuteVerb(Index)
  else begin
    NewIndex := Index - inherited GetVerbCount;
    case NewIndex of
      0: TPanel(Component).Align := alClient;
      1: TPanel(Component).Align := alBottom;
    end;
  end;
end;

Next page > Property 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?

©2014 About.com. All rights reserved.