Upload
juan-carlos-magana-rodriguez
View
6
Download
1
Embed Size (px)
Citation preview
Custom Component Editors in Delphi 7 - #1
When you start to work with custom components after a while you will realize what standard propertyeditors and component editors is not enough.You will need to create your own editor. There is a lot of articles available how to implement Propertyeditor 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 atechnique of creating one. Editor created is a simple property editor for the component name. Eventhough it is not most useful functionality possible, but we hope it will give you an idea and shows a wayhow 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 inDelphi 7 this file was split in two: DesignEditors, DesignIntf. Also you need to know where this files couldbe found. When you create your own editors you start to use functionality of Delphi IDE and in this caseyou have to include DesignIDE.DCP in Requires list for your DPK. You will soon find out what such filedoesn't exist. Instead there is only designide70.bpl in "X:Program FilesBorlandDelphi7Bin" folder. Don'tworry, file is actually in "X:Program FilesBorlandDelphi7Lib" folder. So, you will need to add it from thislocation. 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 FieldList 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'tworry 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 andchoose 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 editorfor 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;
interfaceuses
DesignEditors, DesignIntf, Classes, uMyDesigner;
typeTMyComponentEditor = 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;implementationuses
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. Butlet looks at a code. Our TMyComponentEditor is a main class here. This class allow us specify a logic IDEwill invoke when we Dbl-click on component or use Context menu.
We define function GetDesignerClass: TMyCmpDesignerClass; virtual ; method here just in case if in afuture 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); willinclude just one line which invokes editor. You can extend Editor class to add more action but will notdiscuss 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 andchange 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 fromarticles 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 samedesigner and update it when change a focus, you will need to handle update of a state when you switchbetween a components.So, to implement a designer you will need to modify your component code like this (I did highlight it withRed color). And add stub for Designer class. This code is only used by component to communicate withdesigner. We will have real implementation defined later by inheriting from TMyComponentDesigner.
TMyComponentDesigner = class;Tcomponent1 = class(TComponent)private
FDesigner: TMyComponentDesigner;procedure SetDesigner(const Value: TMyComponentDesigner);
protectedpublic
destructor Destroy; override;property Designer: TMyComponentDesigner read FDesigner write SetDesigner;
publishedend;TMyComponentDesigner = class(TObject)private
FComponent: Tcomponent1;protectedpublic
constructor Create(Component: Tcomponent1); virtual;destructor Destroy; override;procedure Modified; virtual;property Component: Tcomponent1 read FComponent;
end;constructor TMyComponentDesigner.Create(Component: Tcomponent1);begininherited Create;FComponent := Component;FComponent.Designer := Self;
end;destructor TMyComponentDesigner.Destroy;beginFComponent.Designer := nil;inherited;
end;procedure TMyComponentDesigner.Modified;begin// stubend;{ Tcomponent1 }
procedure Tcomponent1.SetDesigner(const Value: TMyComponentDesigner);begin
FDesigner := Value;end;destructor Tcomponent1.Destroy;begin
if Assigned(fDesigner) thenFreeAndNil(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 willneed to replace class(TForm) on class(TDesignWindow). You can do it by modifying a sourcecode for this class.
• You will also implement 3 properties in this formproperty ComponentDesigner: TMyCmpDesigner read FComponentDesigner write SetComponentDesigner;property Component : Tcomponent1 read FComponent write SetComponent;property ComponentDesignerClass : TMyCmpDesignerClass read FCmpDesignerClass write SetCmpDesignerClass;
• And add implementation for ShowComponentEditor procedure we've saw before.
There is a source for this unit:unit uMyDesigner;
interfaceuses
DesignIntf, DesignWindows, // designer unitsuMyComponent, // component implementationForms, StdCtrls, Classes, Controls; // Form units
typeTMyCmpDesigner = class;TMyCmpDesignerClass = class of TMyCmpDesigner;
// Designer form declarationTfrmCmpDesigner = class(TDesignWindow)
Button1: TButton;Edit1: TEdit;procedure Button1Click(Sender: TObject);procedure FormClose(Sender: TObject; var Action: TCloseAction);
privateFCmpDesignerClass: TMyCmpDesignerClass;FComponent: Tcomponent1;
procedure SetComponentDesigner(const Value: TMyCmpDesigner);procedure SetCmpDesignerClass(const Value: TMyCmpDesignerClass);procedure SetComponent(const Value: Tcomponent1);
protectedFComponentDesigner: TMyCmpDesigner;
publicdestructor Destroy; override;property ComponentDesigner: TMyCmpDesigner read FComponentDesigner write SetComponentDesigner;property Component : Tcomponent1 read FComponent write SetComponent;property ComponentDesignerClass : TMyCmpDesignerClass read FCmpDesignerClass write SetCmpDesignerClass;
end;// designer implementationTMyCmpDesigner = class(TMyComponentDesigner)private
FComponentDesigner: TfrmCmpDesigner;
procedure SetComponentDesigner(const Value: TfrmCmpDesigner);public
destructor Destroy; override;procedure Modified; override;property ComponentDesigner: TfrmCmpDesigner read FComponentDesigner write SetComponentDesigner;
end;procedure ShowComponentEditor(aDesigner: IDesigner; aComponent: TComponent1; aDesignerClass: TMyCmpDesignerClass);function CreateComponentEditor(aDesigner: IDesigner; aComponent: TComponent1; aDesignerClass: TMyCmpDesignerClass; var aShared: Boolean): TfrmCmpDesigner;
implementation{$R *.dfm}
procedure ShowComponentEditor(aDesigner: IDesigner; aComponent: TComponent1; aDesignerClass: TMyCmpDesignerClass);var
lCmpDesigner: TfrmCmpDesigner;lShared: Boolean;
beginlCmpDesigner := CreateComponentEditor(aDesigner, aComponent, aDesignerClass, lShared);if lCmpDesigner <> nil then
lCmpDesigner.Show;end;function CreateComponentEditor(aDesigner: IDesigner; aComponent: Tcomponent1; aDesignerClass: TMyCmpDesignerClass; var aShared: Boolean): TfrmCmpDesigner;begin
aShared := True;if aComponent.Designer <> nil then
Result := (aComponent.Designer as TMyCmpDesigner).ComponentDesignerelsebegin
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 thenbegin
FComponentDesigner.ComponentDesigner := nil;FComponentDesigner.Release;
end;inherited;
end;procedure TMyCmpDesigner.Modified;
begininherited;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 thenbegin
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 thenbegin
if FComponent <> nil thenbegin
FComponentDesigner.Free;FComponentDesigner := nil;
end;FComponent := Value;
if FComponent <> nil thenbegin
FComponentDesigner := FCmpDesignerClass.Create(Value);FComponentDesigner.FComponentDesigner := Self;Caption := FComponent.Name;Edit1.Text := FComponent.Name;
endelsebegin
Release;end;
end;end;
procedure TfrmCmpDesigner.SetComponentDesigner(const Value: TMyCmpDesigner);begin
FComponentDesigner := Value;end;procedure TfrmCmpDesigner.Button1Click(Sender: TObject);begin
if Assigned(FComponent) thenbegin
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 willgive 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 andtraining services for Delphi, MSSQL Server, Wise Installation System and Wise for Windows Installersince 1996.For information about on-site training and consulting you can contact author via email Serge or visit hisWeb site at www.dragonsoft.us.
Copyright © 2008 Serge DosyukovALL RIGHTS RESERVED. NO PART OF THIS DOCUMENT CAN BE COPIED IN ANY FORM WITHOUTTHE EXPRESS, WRITTEN CONSENT OF THE AUTHOR