You are on page 1of 9

Custom Component Editors in Delphi 7 - #1

When you start to work with custom components after a while you will realize what standard property editors and component editors is not enough. You will need to create your own editor. There is a lot of articles available how to implement Property editor or even Component Editor. There is some links: http://delphi.about.com/library/bluc/text/uc092501a.htm http://www.drbob42.com/delphi/property.htm They cover basis you will need to know when trying to create your own editor. Note. This article is not intended to give you a feature complete Component Editor, but demonstrates a technique of creating one. Editor created is a simple property editor for the component name. Even though it is not most useful functionality possible, but we hope it will give you an idea and shows a way how to create one of the best one for your own use. Note. If you see code where DsgnIntf file is in uses clause of the unit, you will need to remember what in Delphi 7 this file was split in two: DesignEditors, DesignIntf. Also you need to know where this files could be found. When you create your own editors you start to use functionality of Delphi IDE and in this case you have to include DesignIDE.DCP in Requires list for your DPK. You will soon find out what such file doesn't exist. Instead there is only designide70.bpl in "X:Program FilesBorlandDelphi7Bin" folder. Don't worry, file is actually in "X:Program FilesBorlandDelphi7Lib" folder. So, you will need to add it from this location. After this operation you will be able to use IDE design functionality such as editors and ToolAPI. Instead in this article I'm going to talk about Component Form Editor. Example of such editor is a Field List editor for TDataSet based components. Lets look at a source file DSDesign.pas in "X:Program FilesBorlandDelphi7SourceProperty Editors" folder. Most of implementation is here. If you will drill down to a code you will think what it is a lot of code to implement something like this. Don't worry it is much easier you could imagine. And in couple minutes you will agree with this.

Step 1 - Create a component


Oh, I don't think I need to spend a lot of time here. Open your Delphi Create new ProjectGroup - Delphi MenuFileNewOther... - select Project Group in "New" tab Add new package to your project group - Delphi MenuProjectAdd New Project - select Package new package will be added into group Activate a project Add a new component into package - Delphi MenuComponentNew Component. You can inherit from any existing class, we will use TComponent as an Ancestor. Name it MyComponent and choose to install it. Delphi will generate all necessary code. Save it as a uMyComponent.pas file. Ok, now we are ready for next step.

Step 2 - Create Component Editor


We need to add and register an editor for our component. In this article we will implement Dbl-Click editor for our component. Add a new unit file into package and name it uMyEditor.pas You will need to implement TComponentEditor descendant, there is a source code for this file unit uMyEditor; interface uses DesignEditors, DesignIntf, Classes, uMyDesigner; type TMyComponentEditor = class(TComponentEditor) protected function GetDesignerClass: TMyCmpDesignerClass; virtual; public procedure Edit; override; procedure ExecuteVerb(Index: Integer); override; function GetVerb(Index: Integer): string; override; function GetVerbCount: Integer; override; end; procedure Register; implementation uses uMyComponent; procedure Register; begin RegisterComponentEditor(TComponent1, TMyComponentEditor); end; { TMyComponentEditor } procedure TMyComponentEditor.Edit; begin ShowComponentEditor(Designer, Tcomponent1(Component), GetDesignerClass); end; procedure TMyComponentEditor.ExecuteVerb(Index: Integer); begin Edit; end; function TMyComponentEditor.GetDesignerClass: TMyCmpDesignerClass; begin Result := TMyCmpDesigner; end;

function TMyComponentEditor.GetVerb(Index: Integer): string; begin Result := 'My Editor'; end; function TMyComponentEditor.GetVerbCount: Integer; begin Result := 1; end; end. You will not be able to compile it, because we do use ShowComponentEditor which is not defined yet. But let looks at a code. Our TMyComponentEditor is a main class here. This class allow us specify a logic IDE will invoke when we Dbl-click on component or use Context menu. We define function GetDesignerClass: TMyCmpDesignerClass; virtual ; method here just in case if in a future we will want to customize more our editor and introduce descendant from it. If you have just one action handled procedure TMyComponentEditor.ExecuteVerb(Index: Integer); will include just one line which invokes editor. You can extend Editor class to add more action but will not discuss it here. Please see examples from a links I mentioned before. Important part of the code is where we invoke an Designer: ShowComponentEditor(Designer, Tcomponent1(Component), GetDesignerClass); This procedure will be defined on a next step. For now I only want describe a parameters we pass into it. Designer - this is a link to current IDesigner interface which allows your code to communicate with IDE. Component - is a link to an instance of component you Dbl-click on a form and allows to obtain and change information from our designer. GetDesignerClass - return information about a class for Designer which will be used to handle our logic. You can put just a class name here instead to call a function we defined. Now we almost ready to create a designer itself.

Step 3 - Create Component communicator


This is important part. Major difference between implementation we will discuss here and one from articles you looked before is - we will create a Form based designer. When Designer of this type is invoked, IDE will create and show a form with some logic implemented in it. Form will be created for each instance of the component you work with. If you would like to share same designer and update it when change a focus, you will need to handle update of a state when you switch between a components. So, to implement a designer you will need to modify your component code like this (I did highlight it with Red color). And add stub for Designer class. This code is only used by component to communicate with designer. We will have real implementation defined later by inheriting from TMyComponentDesigner. TMyComponentDesigner = class; Tcomponent1 = class(TComponent) private FDesigner: TMyComponentDesigner; procedure SetDesigner(const Value: TMyComponentDesigner); protected public

destructor Destroy; override; property Designer: TMyComponentDesigner read FDesigner write SetDesigner; published end; TMyComponentDesigner = class(TObject) private FComponent: Tcomponent1; protected public constructor Create(Component: Tcomponent1); virtual; destructor Destroy; override; procedure Modified; virtual; property Component: Tcomponent1 read FComponent; end; constructor TMyComponentDesigner.Create(Component: Tcomponent1); begin inherited Create; FComponent := Component; FComponent.Designer := Self; end; destructor TMyComponentDesigner.Destroy; begin FComponent.Designer := nil; inherited; end; procedure TMyComponentDesigner.Modified; begin // stub end; { Tcomponent1 } procedure Tcomponent1.SetDesigner(const Value: TMyComponentDesigner); begin FDesigner := Value; end; destructor Tcomponent1.Destroy; begin if Assigned(fDesigner) then FreeAndNil(fDesigner); inherited; end;

Step 3 - Create Component Designer


Now we are ready to implement a designer.

Add new form to your package. Save a file as uMyDesigner.pas Now there is a little trick. Designer Form is not just a regular form. It should be derived from TDesignWindow to implement interfaces available for you to communicate with IDE. You will need to replace class(TForm) on class(TDesignWindow). You can do it by modifying a source code for this class. You will also implement 3 properties in this form property ComponentDesigner: TMyCmpDesigner read FComponentDesigner write SetCompon property Component : Tcomponent1 read FComponent write SetComponent; property ComponentDesignerClass : TMyCmpDesignerClass read FCmpDesignerClass write And add implementation for ShowComponentEditor procedure we've saw before. There is a source for this unit: unit uMyDesigner; interface uses DesignIntf, DesignWindows, // designer units uMyComponent, // component implementation Forms, StdCtrls, Classes, Controls; // Form units type TMyCmpDesigner = class; TMyCmpDesignerClass = class of TMyCmpDesigner; // Designer form declaration TfrmCmpDesigner = class(TDesignWindow) Button1: TButton; Edit1: TEdit; procedure Button1Click(Sender: TObject); procedure FormClose(Sender: TObject; var Action: TCloseAction); private FCmpDesignerClass: TMyCmpDesignerClass; FComponent: Tcomponent1; procedure SetComponentDesigner(const Value: TMyCmpDesigner); procedure SetCmpDesignerClass(const Value: TMyCmpDesignerClass); procedure SetComponent(const Value: Tcomponent1); protected FComponentDesigner: TMyCmpDesigner; public destructor Destroy; override;

property ComponentDesigner: TMyCmpDesigner read FComponentDesigner write SetComponentD property Component : Tcomponent1 read FComponent write SetComponent; property ComponentDesignerClass : TMyCmpDesignerClass read FCmpDesignerClass write Set end; // designer implementation TMyCmpDesigner = class(TMyComponentDesigner) private FComponentDesigner: TfrmCmpDesigner;

procedure SetComponentDesigner(const Value: TfrmCmpDesigner); public destructor Destroy; override; procedure Modified; override;

property ComponentDesigner: TfrmCmpDesigner read FComponentDesigner write SetComponent end;

procedure ShowComponentEditor(aDesigner: IDesigner; aComponent: TComponent1; aDesignerClas function CreateComponentEditor(aDesigner: IDesigner; aComponent: TComponent1; aDesignerCla implementation {$R *.dfm}

procedure ShowComponentEditor(aDesigner: IDesigner; aComponent: TComponent1; aDesignerClas var lCmpDesigner: TfrmCmpDesigner; lShared: Boolean; begin lCmpDesigner := CreateComponentEditor(aDesigner, aComponent, aDesignerClass, lShared); if lCmpDesigner <> nil then lCmpDesigner.Show; end;

function CreateComponentEditor(aDesigner: IDesigner; aComponent: Tcomponent1; aDesignerCla begin aShared := True; if aComponent.Designer <> nil then Result := (aComponent.Designer as TMyCmpDesigner).ComponentDesigner else begin Result := TfrmCmpDesigner.Create(Application); Result.ComponentDesignerClass := aDesignerClass; Result.Designer := aDesigner; Result.Component := aComponent; aShared := False; end; end; { TMyCmpDesigner } destructor TMyCmpDesigner.Destroy; begin if FComponentDesigner <> nil then begin FComponentDesigner.ComponentDesigner := nil; FComponentDesigner.Release; end; inherited; end; procedure TMyCmpDesigner.Modified;

begin inherited; if Assigned(FComponentDesigner) and Assigned(FComponentDesigner.Designer) then FComponentDesigner.Designer.Modified; end; procedure TMyCmpDesigner.SetComponentDesigner(const Value: TfrmCmpDesigner); begin FComponentDesigner := Value; end; { TfrmCmpDesigner } destructor TfrmCmpDesigner.Destroy; begin if FComponentDesigner <> nil then begin FComponentDesigner.ComponentDesigner := nil; FComponentDesigner.Free; end; inherited; end; procedure TfrmCmpDesigner.SetCmpDesignerClass(const Value: TMyCmpDesignerClass); begin FCmpDesignerClass := Value; end; procedure TfrmCmpDesigner.SetComponent(const Value: Tcomponent1); begin if FComponent <> Value then begin if FComponent <> nil then begin FComponentDesigner.Free; FComponentDesigner := nil; end; FComponent := Value; if FComponent <> nil then begin FComponentDesigner := FCmpDesignerClass.Create(Value); FComponentDesigner.FComponentDesigner := Self; Caption := FComponent.Name; Edit1.Text := FComponent.Name; end else begin Release; end; end; end;

procedure TfrmCmpDesigner.SetComponentDesigner(const Value: TMyCmpDesigner); begin FComponentDesigner := Value; end; procedure TfrmCmpDesigner.Button1Click(Sender: TObject); begin if Assigned(FComponent) then begin FComponent.Name := Edit1.Text; Designer.Modified; end; Close; end; procedure TfrmCmpDesigner.FormClose(Sender: TObject; var Action: TCloseAction); begin Action := caFree; end; end.

Installation
... We have a package ready for compile. Link to full source code for this article. After package is installed in IDE and component is ready for use. Our Designer will allow to change a Name property of the component via form. It is just example but it will give you an idea where to go. Notes. 1. Remember to call Designer.Modified; to let associated component Object Inspector know what you did modify some of the properties and Inspector should repopulate values. 2. Our Designer form is created as float form and will stay opened even you did change property or component focus. Any modification you will make in it will be reflected by associated component

7. Links
This article on the web This article on the BDN Sample project

About the Author


Serge Dosyukov is a founder of Dragon Software, a consulting company which provides a consulting and training services for Delphi, MSSQL Server, Wise Installation System and Wise for Windows Installer since 1996. For information about on-site training and consulting you can contact author via email Serge or visit his Web site at www.dragonsoft.us.

Copyright 2008 Serge Dosyukov ALL RIGHTS RESERVED. NO PART OF THIS DOCUMENT CAN BE COPIED IN ANY FORM WITHOUT THE EXPRESS, WRITTEN CONSENT OF THE AUTHOR