Creating Form and Project Wizards

From RAD Studio
(Redirected from Creating Forms and Projects)
Jump to: navigation, search

Go Up to Using Special Interfaces to Extend the IDE


Delphi comes with a number of form and project wizards already installed, and you can write your own. The Object Repository lets you create static templates that can be used in a project, but a wizard offers much more power because it is dynamic. The wizard can prompt the user and create different kinds of files depending on the user's responses.

A form or project wizard typically creates one or more new files. Instead of real files, however, it is best to create unnamed, unsaved modules. When the user saves them, the IDE prompts the user for a file name. A wizard uses a creator object to create such modules.

A creator class implements a creator interface, which inherits from IOTACreator. The wizard passes a creator object to the module service's CreateModule method, and the IDE calls back to the creator object for the parameters it needs to create the module.

For example, a form wizard that creates a new form typically implements GetExisting to return false and GetUnnamed to return true. This creates a module that has no name (so the user must pick a name before the file can be saved) and is not backed by an existing file (so the user must save the file even if the user does not make any changes). Other methods of the creator tell the IDE what kind of file is being created (e.g., project, unit, or form), provide the contents of the file, or return the form name, ancestor name, and other important information. Additional callbacks let a wizard add modules to a newly created project, or add components to a newly created form.

To create a new file, which is often required in a form or project wizard, you usually need to provide the contents of the new file. To do so, write a new class that implements the IOTAFile interface. If your wizard can make do with the default file contents, you can return nil from any function that returns IOTAFile.

For example, suppose your organization has a standard comment block that must appear at the top of each source file. You could do this with a static template in the Object Repository, but the comment block would need to be updated manually to reflect the author and creation date. Instead, you can use a creator to dynamically fill in the comment block when the file is created.

The first step is to write a wizard that creates new units and forms. Most of the creator's functions return zero, empty strings, or other default values, which tells the Tools API to use its default behavior for creating a new unit or form. Override GetCreatorType to inform the Tools API what kind of module to create: a unit or a form. To create a unit, return sUnit. To create a form, return sForm. To simplify the code, use a single class that takes the creator type as an argument to the constructor. Save the creator type in a data member, so that GetCreatorType can return its value. Implement NewImplSource and NewIntfSource to return the desired file contents.

TCreator = class(TInterfacedObject, IOTAModuleCreator)
public
constructor Create(const CreatorType: string);
  { IOTAModuleCreator }
  function GetAncestorName: string;
  function GetImplFileName: string;
  function GetIntfFileName: string;
  function GetFormName: string;
  function GetMainForm: Boolean;
  function GetShowForm: Boolean;
  function GetShowSource: Boolean;
  function NewFormFile(const FormIdent, AncestorIdent: string): IOTAFile;
  function NewImplSource(const ModuleIdent, FormIdent, AncestorIdent: string): IOTAFile;
  function NewIntfSource(const ModuleIdent, FormIdent, AncestorIdent: string): IOTAFile;
  procedure FormCreated(const FormEditor: IOTAFormEditor);
  { IOTACreator }
  function GetCreatorType: string;
  function GetExisting: Boolean;
  function GetFileSystem: string;
  function GetOwner: IOTAModule;
  function GetUnnamed: Boolean;
private
  FCreatorType: string;
end;
class PACKAGE Creator : public IOTAModuleCreator {
public:
__fastcall Creator(const AnsiString creator_type)
: ref_count(0), creator_type(creator_type) {}
virtual __fastcall ~Creator();
// IOTAModuleCreator
virtual AnsiString __fastcall GetAncestorName();
virtual AnsiString __fastcall GetImplFileName();
virtual AnsiString __fastcall GetIntfFileName();
virtual AnsiString __fastcall GetFormName();
virtual bool __fastcall GetMainForm();
virtual bool __fastcall GetShowForm();
virtual bool __fastcall GetShowSource();
virtual _di_IOTAFile __fastcall NewFormFile(
const AnsiString FormIdent, const AnsiString AncestorIdent);
virtual _di_IOTAFile __fastcall NewImplSource(
const AnsiString ModuleIdent, const AnsiString FormIdent,
const AnsiString AncestorIdent);
virtual _di_IOTAFile __fastcall NewIntfSource(
const AnsiString ModuleIdent, const AnsiString FormIdent,
const AnsiString AncestorIdent);
virtual void __fastcall FormCreated(
const _di_IOTAFormEditor FormEditor);
// IOTACreator
virtual AnsiString __fastcall GetCreatorType();
 virtual bool __fastcall GetExisting();
virtual AnsiString __fastcall GetFileSystem();
virtual _di_IOTAModule __fastcall GetOwner();
virtual bool __fastcall GetUnnamed();
protected:
// IInterface
virtual HRESULT __stdcall QueryInterface(const GUID&, void**);
virtual ULONG __stdcall AddRef();
virtual ULONG __stdcall Release();
private:
long ref_count;
const AnsiString creator_type;
};

Most of the members of TCreator return zero, nil, or empty strings. The boolean methods return true, except GetExisting, which returns false. The most interesting method is GetOwner, which returns a pointer to the current project module, or nil if there is no project. There is no simple way to discover the current project or the current project group. Instead, GetOwner must iterate over all open modules. If a project group is found, it must be the only project group open, so GetOwner returns its current project. Otherwise, the function returns the first project module it finds, or nil if no projects are open.

function TCreator.GetOwner: IOTAModule;
var
  I: Integer;
  Svc: IOTAModuleServices;
  Module: IOTAModule;
  Project: IOTAProject;
  Group: IOTAProjectGroup;
begin
  { Return the current project. }
  Supports(BorlandIDEServices, IOTAModuleServices, Svc);
  Result := nil;
  for I := 0 to Svc.ModuleCount - 1 do
  begin
    Module := Svc.Modules[I];
    if Supports(Module, IOTAProject, Project) then
    begin
      { Remember the first project module}
      if Result = nil then
        Result := Project;
    end
    else if Supports(Module, IOTAProjectGroup, Group) then
    begin
      { Found the project group, so return its active project}
      Result := Group.ActiveProject;
      Exit;
    end;
  end;
end;
_di_IOTAModule __fastcall Creator::GetOwner()
{
// Return the current project.
_di_IOTAProject result = 0;
  _di_IOTAModuleServices svc = interface_cast<IOTAModuleServices>(BorlandIDEServices);
  for (int i = 0; i < svc->ModuleCount; ++i)
  begin
    _di_IOTAModule module = svc->Modules[i];
_di_IOTAProject project;
_di_IOTAProjectGroup group;
if (module->Supports(project)) {
// Remember the first project module.
if (result == 0)
result = project;
} else if (module->Supports(group)) {
// Found the project group, so return its active project.
result = group->ActiveProject;
break;
}
}
return result;
}

The creator returns nil from NewFormSource, to generate a default form file. The interesting methods are NewImplSource and NewIntfSource, which create an IOTAFile instance that returns the file contents.

The TFile class implements the IOTAFile interface. It returns -1 as the file age (which means the file does not exist), and returns the file contents as a string. To keep the TFile class simple, the creator generates the string, and the TFile class simply passes it on.

TFile = class(TInterfacedObject, IOTAFile)
public
constructor Create(const Source: string);
  function GetSource: string;
  function GetAge: TDateTime;
private
  FSource: string;
end;
constructor TFile.Create(const Source: string);
begin
  FSource := Source;
end;
  function TFile.GetSource: string;
begin
  Result := FSource;
end;
  function TFile.GetAge: TDateTime;
begin
  Result := TDateTime(-1);
end;
class File : public IOTAFile {
public:
__fastcall File(const AnsiString source);
virtual __fastcall ~File();
AnsiString __fastcall GetSource();
System::TDateTime __fastcall GetAge();
protected:
// IInterface
virtual HRESULT __stdcall QueryInterface(const GUID&, void**);
virtual ULONG __stdcall AddRef();
virtual ULONG __stdcall Release();
private:
long ref_count;
AnsiString source;
};
__fastcall File::File(const AnsiString source)
: ref_count(0), source(source)
{}
AnsiString __fastcall File::GetSource()
{
return source;
}
System::TDateTime __fastcall File::GetAge()
{
return -1;
}

You can store the text for the file contents in a resource to make it easier to modify, but for the sake of simplicity, this example hardcodes the source code in the wizard. The example below generates the source code, assuming there is a form. You can easily add the simpler case of a plain unit. Test FormIdent, and if it is empty, create a plain unit; otherwise create a form unit. The basic skeleton for the code is the same as the IDE's default (with the addition of the comments at the top, of course), but you can modify it any way you desire.

function TCreator.NewImplSource(
                    const ModuleIdent, FormIdent, AncestorIdent: string): IOTAFile;
var
  FormSource: string;
begin
  FormSource :=
  '{ ----------------------------------------------------------------- ' +   #13#10 +
  '%s - description'+   #13#10 +
  'Copyright ©  %y Your company, inc.'+   #13#10 +
  'Created on %d'+   #13#10 +
  'By %u'+   #13#10 +
  ' ----------------------------------------------------------------- }' +   #13#10 +   #13#10;
  return TFile.Create(Format(FormSource, ModuleIdent, FormIdent,
AncestorIdent));
}
_di_IOTAFile __fastcall Creator::NewImplSource(
const AnsiString ModuleIdent,
const AnsiString FormIdent,
const AnsiString AncestorIdent)
{
const AnsiString form_source =
"/*-----------------------------------------------------------------\n"
" %m - description\n"
" Copyright \xa9  %y Your company, inc.\n"
" Created on %d\n"
" By %u\n"
"  ---------------------------------------------------------------*/\n"
"\n"
"#include <vcl.h>\n"
"#pragma hdrstop\n"
"\n"
"#include \"%m.h\"\n"
"//-----------------------------------------------------------------\n"
"#pragma package(smart_init)\n"
"#pragma resource \"*.dfm\"\n"
"T%f *%f;\n"
"//-----------------------------------------------------------------\n"
"__fastcall T%m::T%m(TComponent* Owner)\n"
"        : T%a(Owner)\n"
"{\n"
"}\n"
"//----------------------------------------------------------------\n";
  return new File(expand(form_source, ModuleIdent, FormIdent,
AncestorIdent));
}

The final step is to create two form wizards: one uses sUnit as the creator type, and the other uses sForm. As an added benefit for the user, you can use INTAServices to add a menu item to the File > New menu to invoke each wizard. The menu item's OnClick event handler can call the wizard's Execute function.

Some wizards need to enable or disable the menu items, depending on what else is happening in the IDE. For example, a wizard that checks a project into a source code control system should disable its Check In menu item if no files are open in the IDE. You can add this capability to your wizard by Notifying a Wizard of IDE Events.

See Also