Creating a Form Wizard - Part 2

In the last article, I described a way to make a TForm (or TFrame) descendant and register it with Delphi as a custom module.  This is all well and good, but forms and frames don’t appear in the tool box like other Delphi components.  They appear in the repository.  So, how to make the custom form or frame appear in the repository as though it were a native Delphi component?

The answer lies in the Open Tools API (OTA).  You need to create a Form Wizard using this poorly documented interface to Delphi’s IDE.  The coding itself is not particularly difficult but finding up to date documentation for the OTA is next to impossible.  Here is what I have been able to find out after a lot of googling.

A wizard is a package that you write that hooks into Delphi’s IDE.  The hooks provided by the OTA allow you to do all kinds of things; add a new menu item to the IDE’s menu, add a new feature to the IDE, add items to the repository, etc.  A form wizard, which is what we are interested in, allows you to add a form or frame to the repository.  A form wizard is just a specialized case of a repository wizard, which allows you to add just about anything to the repository.

To create a new form wizard you need take the following steps:

  1. Create a package to hold the wizard
  2. Add a unit to the package to hold the code for the wizard.  You can create multiple wizards in a single package as I did.  One package holds both my form and frame wizards.
  3. Add the code to register the wizard(s) with Delphi.
  4. Install the package.

Create the Package

Creating the package is easy enough, just create add a new package to your project by selecting File | New from the menu and choosing Package from the repository.  Then add Designide.dcp to the Requires section of the package.

Create the Unit

Now just add a unit to your package by selecting File | New from the menu and choosing Unit from the repository.  Now we start to get into the meat of creating your own form wizard.

In order to implement a form wizard you need to create three classes: the form wizard, a module creator and a source file.  The form wizard provides the information that Delphi needs to display an icon for your wizard in the repository.  This includes things like the wizard name, the icon, etc.  The module creator gets executed when your wizard is invoked and creates the source code for the source module that gets created for the new form.  This source file class seems pretty redundant to me, since the only thing it needs to do is save the unit’s source code on creation and parrot it back as required by the IDE.

Create the Form Wizard

Your form wizard will descend from TNotifierObject and implement the IOTAWizard, IOTARepositoryWizard, OTARepositoryWizard60, IOTARepositoryWizard80 and IOTAFormWizard interfaces.  The definitions for these can all be found in the ToolsAPI module.  You need to implement the following interface methods in your code:

IOTAWizard

function GetIDString: String;

This function must return a unique identifier for your wizard.  Convention dictates that this string begins with your company identifier.  For example, the identifier returned for my form wizard is ‘LNS.StreamableFormWizard’.

function GetName: String;

This function must return the name of your wizard.  This is used in error messages and is the value displayed under the icon in the repository.  For example, ‘LNS Streamable Form’.

function GetState: TWizardState;

This function is only used by menu wizards.  For form wizards, this function should return an empty set.

procedure Execute;

This procedure gets executed when you wizard is invoked by the user double clicking the icon in the repository.  For a form wizard, you need call the OTA’s CreateModule method with an instance of your module creator class.  For example,

(BorlandIDEServices as IOTAModuleServices).CreateModule(TlnsStreamableFormCreator.Create);

IOTARepositoryWizard

function GetAuthor: String;

This function returns the author’s name.  This is displayed in the Author column when the repository is in Detail mode.  For example, ‘LNS Software Systems’.

function GetComment: String;

This function returns a comment.  This is displayed in the Comment column when the repository is in Detail mode.  For example, ‘A Streamable form that can save and reload its component’’s  contents to / from an XML stream’.

function GetPage: String;

In older versions of Delphi, this function returns the name of the repository page where your wizard will appear.  In newer versions of Delphi this value is ignored if the GetGalleryCategory function is implemented.  For example, ‘New’;

function GetGlyph: Cardinal;

This function returns a handle to the icon to be displayed form your wizard in the repository.  The icon resouce must contain a 32×32 AND a 16×16 icon.  For example,


Result := LoadIcon(hInstance, 'LNSSTREAMABLEFORM');
if (Result = 0) then
MessageBox(0, 'Could not load icon LNSSTREAMABLEFORM', '', MB_OK);

IOTARepositoryWizard60

function GetDesigner: String;

According the the comments in ToolsAPI:

This function should return the appropriate designer affinity for which this wizard is applicable.  This will help the File|New|Other… dialog filter the appropriate items based on the current project either CLX or VCL.  See the dVCL, dCLX, and dAny constants.  Again according the ToolsAPI, these are the values you can choose from:

The following constants define the currently available form designers. Use dAny for a wizard that doesn’t care under which designer it is invoked.

dVCL = ‘dfm’;
dCLX = ‘xfm’;
dVCLNet = ‘nfm’;
dDotNet = ‘.NET’;
dHTML = ‘HTML’;
dAny = ‘Any’;

Since my wizard was for the Win32 personality, I chose Result := dVcl.

IOTARepositoryWizard80

function GetPersonality: String;

This function returns a string indicating which personality the wizard is valid for.  I couldn’t find any documentation for this function, but the valid personalities seem to be:

sDelphiPersonality = ‘Delphi.Personality’;
sDelphiDotNetPersonality = ‘DelphiDotNet.Personality’;
sCBuilderPersonality = ‘CPlusPlusBuilder.Personality’;
sCSharpPersonality = ‘CSharp.Personality’;
sVBPersonality = ‘VB.Personality’;
sDesignPersonality = ‘Design.Personality’;
sGenericPersonality = ‘Generic.Personality’;

Since my wizard was for Delphi for Win32 I chose Result := sDelphiPersonality.

function GetGalleryCategory: IOTAGalleryCategory;

I wasn’t able to find any documentation on this function either.  I did manage to find sample code for a form wizard that implemented this function so I could figure out how to put my wizard on one of the pre-defined repository pages.  The interfaces involved don’t seem that complex and a bit of experimentation would probably result in a eureka moment that would allow new categories to be added.  But since I wanted my form wizard to appear on the ‘New Files’ area of the repository I didn’t bother to dig any further.  The code required to put your wizard on ‘New Files’ page of the repository follows:


var
cat: IOTAGalleryCategory;
catMgr: IOTAGalleryCategoryManager;
begin
catMgr := (BorlandIDEServices as IOTAGalleryCategoryManager);
Assert(Assigned(catMgr));
cat := catMgr.FindCategory(sCategoryDelphiNewFiles);
Assert(Assigned(cat));
Result := cat;
end;

Create the Module Creator

So much for creating a wizard and putting it into the repository.  Now we need to be able to actually do something when the wizard is invoked.  Remember, that in the implementation of the Execute method above, we created an instance of our module creator class and passed it to the IDE’s CreateModule method.  The module creator does all the work required to actually create the form and its source module.  To do that the module create must descend from TInterfacedObject and implement the IOTACreator and IOTAModuleCreator interfaces.  You need to implement the following interface methods in your code:

IOTACreator

function GetCreatorType: String;

This function returns the type of module that you want to create.  You have three choices

  1. sUnit which creates a unit with no forms designer
  2. sForm which creates a unit with a forms designer.  You use this for both form and frame wizards.
  3. sText which creates a raw text unit

For a form wizard you want Result := sForm.

function GetExisting: Boolean;

This function needs to return False if you are creating an new module.  I can’t imaging a situation when you would want to return True.

function GetFileSystem: String;

I have no idea what the return value of this function is used for.  Always return and empty string.

function GetOwner: IOTAModule;

Returns the module that is the owner of the new form.  For forms, the owner is the project.  Here is the code that needs to go here:


var
module: IOTAModule;
newModule: IOTAModule;
begin
module := (BorlandIDEServices as IOTAModuleServices).CurrentModule;
if (Assigned(module)) then
begin
if (module.QueryInterface(IOTAProject, newModule) = S_OK) then
Result := newModule
else if (module.OwnerCount > 0) then
begin
newModule := Module.OwnerModules[0];
if (Assigned(newModule)) then
if (newModule.QueryInterface(IOTAProject, Result) <> S_OK) then
Result := nil;
end;
end;
end;

function GetUnnamed: Boolean;

This function needs to return True to mark the new unit as ‘unnamed’.  This will cause the ‘Save As’ dialogue to be displayed the first time the unit is saved.

IOTAModuleCreator

function GetAncestorName: String;

This function needs to return the name of the ancestor class of the form or frame being created without the leading T.  In my case, since my class was named TlnsStreamableForm the return value was ‘lnsStreamableForm’.

function GetImplFileName: String;

This function returns the implemenation unit file name or an empty string the have Delphi create a new, unique name.  If you choose to provide a file name here, it MUST be a fully qualified file name. I always return an empty string and the new module gets a name like ‘unit1.pas’.

function GetIntfFileName: String;

This function returns the interface unit file name or an empty string the have Delphi create a new, unique name.  If you choose to provide a file name here, it MUST be a fully qualified file name. I always return an empty string and the new module gets a name like ‘unit1.pas’.

function GetFormName: String;

This function returns the name of the new form.  Return an empty string to have Delphi create a new, unique name.  I always return an empty string and Delphi creates a new of like lnsStreamableForm1.

function GetMainForm: Boolean;

If this function returns True, the new form will become the main form of the project.  I can’t imagine why you would want to do this.  I always return False.

function GetShowForm: Boolean;

Return True from this function to have Delphi show the form.  I’m not sure why you wouldn’t want that to happen, so I always return True.

function GetShowSource: Boolean;

Return True from this function to have Delphi show the source.  I’m not sure why you wouldn’t want that to happen, so I always return True.

function NewFormFile(const FormIdent, AncestorIdent: String): IOTAFile;

I have no idea what this does.  Return nil.

function NewImplSource(const ModuleIdent, FormIdent, AncestorIdent: String): IOTAFile;

Returns the source code that is places into the implementation unit.  For Delphi, this is the entire source code.  For C++ this is the implementation code only.  C++ interface code is provided by the NewIntfSource function.

You need to return an instance of a class which implement IOTAFile.  This class just seems to act as a container for this source code, remembering it on creation and returning it when asked by the IDE.  The source code itself is passed as a string to the constructor.

This is what the code needs to look like:


const
src = 'unit %s;'#13#10 +
#13#10 +
'interface'#13#10 +
#13#10 +
'uses'#13#10 +
'  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls,'#13#10 +
'  Forms, Dialogs, lnsStreamableU;'#13#10 +
#13#10 +
'type'#13#10 +
'  T%s = class(T%s)'#13#10 +
'  private'#13#10 +
'    { Private declarations }'#13#10 +
'  public'#13#10 +
'    { Public declarations }'#13#10 +
'  end;'#13#10 +
#13#10 +
'var'#13#10 +
'  %s: T%s;'#13#10 +
#13#10 +
'implementation'#31#10 +
#13#10 +
'{$R *.dfm}'#13#10 +
#13#10 +
'end.'#13#10;
begin
Result := TlnsSourceFile.Create(Format(src,
[ModuleIdent,
FormIdent,
AncestorIdent,
FormIdent,
FormIdent]));
end;

function NewIntfSource(const ModuleIdent, FormIdent, AncestorIdent: String): IOTAFile;

Used for C++ only.  Returns the interface source code.  Works the same as NewImplSource above.

procedure FormCreated(const FormEditor: IOTAFormEditor);

The procedure can be used for whatever code you may need to execute to initialize your module creator.  I just implement this as a do nothing procedure.

Create the Source File Class

Almost done.  All we need to do now is create a class which descends from TInterfacedObject and implements the IOTAFile interface.  You need to implement the following methods:

constructor Create(const Source: String);

The only thing we need to do here is call the inherited constructor and remember the source code.  To do that just assign the value of Source to a private property (that you must define) named FSource.  Like this:  FSource := Source.

function GetSource: String;

Like the name implies, just return the source code remembered in the constructor.  Like this: Result := FSource.

function GetAge: TDateTime;

Return -1 to indicate that this represent a new file.  Like this: Result := -1.

Registering the Wizard

Registering the wizard with the Delphi IDE is similar to registering a custom component.  You need to include a procedure named Register in your unit and within that procedure you need to call the RegisterPackageWizard with an instance of your form wizard as a parameter.  Like this:


procedure Register;
begin
RegisterPackageWizard(TlnsStreamableFormWizard.Create);
end;

You also need to include a definition of the Register procedure in the interface section of your unit or Delphi won’t find it.

Installing the Wizard

To install your wizard into the IDE just select Component | Install Packages from the menu and install your new package as you would any other Delphi package.

Conclusion

If you have followed the above steps religiously, you should have a working wizard.

While none of this is difficult, you certainly need to implement a goodly number of methods to make it work.  If you mess up any one of the method implmentations you may find that you wizard is not appearing in the repository or at least not where you expect it.  Don’t expect Delphi to give you any clues because it won’t.  You just have to double check your code for typos and perservere.  It took me about 2 days to research this and finally get it to work.

Good Luck!!!!

Streamable Delphi Form Wizard Source

2 Responses to “Creating a Form Wizard - Part 2”

  1. Roberto MR Says:

    Very interesting, the source link dont work.

  2. admin Says:

    Thanks for letting me know. It’s fixed.

Leave a Reply