In this chapter I will finish what I started last time and provide the Module Creator code to create new projects in the IDE or more specifically new Open Tools API projects in the IDE.
While I was testing this code I noticed that the IDE did not maintain any custom code with a DPK packages. It seems the IDE maintains all the code in this file so it is futile to try and put custom code in there. I’ve seen this before when trying to conditionally compile using in a Package definition.
Repository Wizard Form
Below is a revised form. When I came to start implementing the full wizard I realised that there should be more information gathered at this stage so the amendments to the form are shown below and I’ll explain the changes in the code below the image.
I’ve changed the definition of the enumerates to add in things that were missing and provided a new record which is used to pass data around instead of creating functions with long parameter list as shown below:
type type TProjectType = ( //ptApplication, ptPackage, ptDLL ); TAdditionalModule = ( amCompilerDefintions, amInitialiseOTAInterface, amUtilityFunctions, amWizardInterface, amCompilerNotifierInterface, amEditorNotifierInterface, amIDENotifierInterface, amKeyboardBindingInterface, amRepositoryWizardInterface, amProjectCreatorInterface, amModuleCreatorInterface ); TAdditionalModules = Set Of TAdditionalModule; TProjectWizardInfo = Record FProjectName : String; FProjectType : TProjectType; FAdditionalModules : TAdditionalModules; FWizardName : String; FWizardIDString : String; FWizardMenu : Boolean; FWizardMenuText : String; FWizardAuthor : String; FWizardDescription : String; End;
Consequently the signature of the Execute
method has changed below while the body of the code contains a few more assignments from edit boxes to fields of the record.
Class Function TfrmRepositoryWizard.Execute(var ProjectWizardInfo : TProjectWizardInfo): Boolean; Const ProjectTypes : Array[Low(TProjectType)..High(TProjectType)] Of String = ( //'Application', 'Package', 'DLL' ); AdditionalModules : Array[Low(TAdditionalModule)..High(TAdditionalModule)] Of String = ( 'Compiler Definitions (Default)', 'Initialise OTA Interface (Default)', 'OTA Utility Functions (Default)', 'Wizard Interface Template', 'Compiler Notifier Interface Template', 'Editor Notifier Interface Template', 'IDE Notifier Interface Template', 'Keyboard Bindings Interface Template', 'Repository Wizard Interface Template', 'Project Creator Interface Template', 'Module Creator Interface Template' ); Var i : TAdditionalModule; iIndex: Integer; j: TProjectType; Begin Result := False; With TfrmRepositoryWizard.Create(Nil) Do Try rgpProjectType.Items.Clear; For j := Low(TProjectType) To High(TProjectType) Do rgpProjectType.Items.Add(ProjectTypes[j]); edtProjectName.Text := 'MyOTAProject'; rgpProjectType.ItemIndex := 0; edtWizardName.Text := 'My OTA Wizard'; edtWizardIDString.Text := 'My.OTA.Wizard'; edtWizardMenuText.Text := 'My OTA Wizard'; edtWizardAuthor.Text := 'Wizard Author'; memWizardDescription.Text := 'Wizard Description'; // Default Modules ProjectWizardInfo.FAdditionalModules := [amCompilerDefintions..amWizardInterface]; For i := Low(TAdditionalModule) To High(TAdditionalModule) Do Begin iIndex := lbxAdditionalModules.Items.Add(AdditionalModules[i]); lbxAdditionalModules.Checked[iIndex] := i In ProjectWizardInfo.FAdditionalModules; End; If ShowModal = mrOK Then Begin ProjectWizardInfo.FProjectName := edtProjectName.Text; ProjectWizardInfo.FProjectType := TProjectType(rgpProjectType.ItemIndex); ProjectWizardInfo.FWizardName := edtWizardName.Text; ProjectWizardInfo.FWizardIDString := edtWizardIDString.Text; ProjectWizardInfo.FWizardMenu := cbxMenuWizard.Checked; ProjectWizardInfo.FWizardMenuText := edtWizardMenuText.Text; ProjectWizardInfo.FWizardAuthor := edtWizardAuthor.Text; ProjectWizardInfo.FWizardDescription := memWizardDescription.Text; For i := Low(TAdditionalModule) To High(TAdditionalModule) Do If lbxAdditionalModules.Checked[Integer(i)] Then Include(ProjectWizardInfo.FAdditionalModules, i) Else Exclude(ProjectWizardInfo.FAdditionalModules, i); Result := True; End; Finally Free; End; End;
The ClickCheck
method has been updated as there are 4 default modules for an OTA project.
procedure TfrmRepositoryWizard.lbxAdditionalModulesClickCheck(Sender: TObject); Var iModule: TAdditionalModule; begin For iModule := amCompilerDefintions To amWizardInterface Do lbxAdditionalModules.Checked[Integer(iModule)] := True; end;
Finally the OK button’s OnClick
event handler has been modified to check that the input of the additional dialogue controls is valid.
procedure TfrmRepositoryWizard.btnOKClick(Sender: TObject); Procedure CheckTextField(strText, strMsg : String); Begin If strText = '' Then Begin MessageDlg(strMsg, mtError, [mbOK], 0); ModalResult := mrNone; Abort; End; End; Var boolProjectNameOK: Boolean; PG : IOTAProjectGroup; i: Integer; begin If Length(edtProjectName.Text) = 0 Then Begin MessageDlg('You must specify a name for the project.', mtError, [mbOK], 0); ModalResult := mrNone; Exit; End; {$IFNDEF D2009} If edtProjectName.Text[1] In ['0'..'9'] Then {$ELSE} If CharInSet(edtProjectName.Text[1], ['0'..'9']) Then {$ENDIF} Begin MessageDlg('The project name must start with a letter or underscore.', mtError, [mbOK], 0); ModalResult := mrNone; Exit; End; boolProjectNameOK := True; PG := ProjectGroup; For i := 0 To PG.ProjectCount - 1 Do If CompareText(ChangeFileExt(ExtractFileName(PG.Projects[i].FileName), ''), edtProjectName.Text) = 0 Then Begin boolProjectNameOK := False; Break; End; If Not boolProjectNameOK Then Begin MessageDlg(Format('There is already a project named "%s" in the project group!', [edtProjectName.Text]), mtError, [mbOK], 0); ModalResult := mrNone; End; CheckTextField(edtWizardName.Text, 'You must specify a Wizard Name.'); CheckTextField(edtWizardIDString.Text, 'You must specify a Wizard ID String.'); CheckTextField(edtWizardMenuText.Text, 'You must specify a Wizard Menu Text.'); end;
Module Creator
Now we moved on to the definition of the Module Creator. This is very similar to the Project Creator from the last chapter and the methods of the interfaces are called by the IDE when the module is created.
TModuleCreator = Class(TInterfacedObject, IOTACreator, IOTAModuleCreator) {$IFDEF D2005} Strict {$ENDIF} Private FProject : IOTAProject; FProjectWizardInfo : TProjectWizardInfo; FAdditionalModule : TAdditionalModule; {$IFDEF D2005} Strict {$ENDIF} Protected Public Constructor Create(AProject : IOTAProject; ProjectWizardInfo : TProjectWizardInfo; AdditionalModule : TAdditionalModule); // IOTACreator Function GetCreatorType: String; Function GetExisting: Boolean; Function GetFileSystem: String; Function GetOwner: IOTAModule; Function GetUnnamed: Boolean; // IOTAModuleCreator Procedure FormCreated(Const FormEditor: IOTAFormEditor); Function GetAncestorName: String; Function GetFormName: String; Function GetImplFileName: String; Function GetIntfFileName: String; Function GetMainForm: Boolean; Function GetShowForm: Boolean; Function GetShowSource: Boolean; Function NewFormFile(Const FormIdent: String; Const AncestorIdent: String) : IOTAFile; Function NewImplSource(Const ModuleIdent: String; Const FormIdent: String; Const AncestorIdent: String): IOTAFile; Function NewIntfSource(Const ModuleIdent: String; Const FormIdent: String; Const AncestorIdent: String): IOTAFile; End;
I’ll talk about the methods further down the page.
Since we are going to specify our own code we will also need the file creator below:
TModuleCreatorFile = Class(TInterfacedObject, IOTAFile) {$IFDEF D2005} Strict {$ENDIF} Private FProjectWizardInfo : TProjectWizardInfo; FAdditionalModule : TAdditionalModule; {$IFDEF D2005} Strict {$ENDIF} Protected Function ExpandMacro(strText, strMacroName, strReplaceText : String) : String; Function GetFinaliseWizardCode : String; Function GetInitialiseWizardCode : String; Function GetVariableDeclCode : String; Function GetUsesClauseCode : String; Public Constructor Create(ProjectWizardInfo : TProjectWizardInfo; AdditionalModule : TAdditionalModule); function GetAge: TDateTime; function GetSource: string; End;
I’ll talk about the methods further down the page.
In order to make this easier I’ve used text files in the projects resources to hold the source code which in turn contains some “macros” that get expanded during creation, (for example $MODULENAME$
is for the module name of the unit).
In order to do this I’ve defined a simple record which allows us to create a static constant array containing all the information we need for resource names and module names that can be called depending on the TAdditionalModule
enumerate passed.
Type TModuleInfo = Record FResourceName : String; FModuleName : String; End; Const strProjectTemplate : Array[Low(TAdditionalModule)..High(TAdditionalModule)] Of TModuleInfo = ( (FResourceName: 'OTAModuleCompilerDefinitions'; FModuleName: 'CompilerDefinitions.inc'), (FResourceName: 'OTAModuleInitialiseOTAInterfaces'; FModuleName: 'InitialiseOTAInterface.pas'), (FResourceName: 'OTAModuleUtilityFunctions'; FModuleName: 'UtilityFunctions.pas'), (FResourceName: 'OTAModuleWizardInterface'; FModuleName: 'WizardInterface.pas'), (FResourceName: 'OTAModuleCompilerNotifierInterface'; FModuleName: 'CompilerNotifierInterface.pas'), (FResourceName: 'OTAModuleEditorNotifierInterface'; FModuleName: 'EditorNotifierInterface.pas'), (FResourceName: 'OTAModuleIDENotifierInterface'; FModuleName: 'IDENotifierInterface.pas'), (FResourceName: 'OTAModuleKeyboardBindingInterface'; FModuleName: 'KeyboardBindingInterface.pas'), (FResourceName: 'OTAModuleRepositoryWizardInterface'; FModuleName: 'RepositoryWizardInterface.pas'), (FResourceName: 'OTAModuleProjectCreatorInterface'; FModuleName: 'ProjectCreatorInterface.pas'), (FResourceName: 'OTAModuleModuleCreatorInterface'; FModuleName: 'ModuleCreatorInterface.pas') );
IOTACreator
The methods of the IOTACreator
interface are the same as those defined in the Project Creator.
Firstly, we define a constructor so that we can pass the project and project information on to the file creator class when actually creating the modules.
constructor TModuleCreator.Create(AProject: IOTAProject; ProjectWizardInfo : TProjectWizardInfo; AdditionalModule : TAdditionalModule); begin FProject := AProject; FProjectWizardInfo := ProjectWizardInfo; FAdditionalModule := AdditionalModule; end;
The GetCreatorType
should returns the string representing the type of module to create. This can be any one of the following strings:
sUnit
: Unit module;sForm
: Form Module;sText
: RAW text module with no code.
In this instance we return sUnit
as we need units for our interface modules.
function TModuleCreator.GetCreatorType: String; begin Result := sUnit; end;
The GetExisting
method tells the IDE if this is an existing module. We are creating a new one so will return false.
function TModuleCreator.GetExisting: Boolean; begin Result := False; end;
The GetFileSystem
method should return an empty string inferring that we are using the default file system.
function TModuleCreator.GetFileSystem: String; begin Result := ''; end;
The GetOwner
method should return the project to which the module should be associated. In this case we return the project passed in the class’s constructor.
function TModuleCreator.GetOwner: IOTAModule; begin Result := FProject; end;
Finally, the GetUnnamed
method should return True to signify that this is a new unsaved module and therefore the IDE should ask the user on the first time of saving as to where they would like to save the file and possibly rename it.
function TModuleCreator.GetUnnamed: Boolean; begin Result := True; end;
IOTAModuleCreator
Now for the methods of the module creator which again are called by the IDE on creation of the module. Note: this interface has not changed and therefore there are no numbered interfaces to implement for different version of Delphi.
The FormCreated
method is called once the new form or data module has been created so that you can manipulate the form by adding controls.
procedure TModuleCreator.FormCreated(const FormEditor: IOTAFormEditor); begin end;
The GetAncestorName
method as far as I’m a where is only called if you are creating a form and this is used as the ancestor for the form.
function TModuleCreator.GetAncestorName: String; begin Result := 'TForm'; end;
The GetFormName
should returns the name of the form when you are creating a form. In the case of a unit this is ignored.
function TModuleCreator.GetFormName: String; begin { Return the form name } Result := 'MyForm1'; end;
The GetImplFileName
method should returns the name of the implementation file (.pas file in Delphi or .cpp file in C++, etc). This must be a fully qualified drive:\path\filename.ext
. You can leave this blank to have the IDE create a new unique one for you.
function TModuleCreator.GetImplFileName: String; begin Result := GetCurrentDir + '\' + strProjectTemplate[FAdditionalModule].FModuleName; end;
The GetIntfFileName
method is only applicable to C++ as Delphi .pas files have their interface section within them. Therefore return an empty string for the IDE to handle this itself.
function TModuleCreator.GetIntfFileName: String; begin Result := ''; end;
The GetMainForm
method should return true when creating a form IF this will be the projects main form. For our exercise this can be false.
function TModuleCreator.GetMainForm: Boolean; begin Result := False; end;
The GetShowForm
method should return true if you want the form to be displayed once created. For our purposes this can be false.
function TModuleCreator.GetShowForm: Boolean; begin REsult := False; end;
The GetShowSource
method should return true if you want the unit to be displayed once created. For our purposes this can be true.
function TModuleCreator.GetShowSource: Boolean; begin Result := True; end;
The NewFormFile
method is where you can provide the source to the DFM file and create you own form. For our purposes this will return Nil.
function TModuleCreator.NewFormFile(const FormIdent, AncestorIdent: String): IOTAFile; begin Result := Nil; end;
The NewImplSource
method is where we return a IOTAFile
interface to create our custom source code for our modules in the same manner as we did for the Project Creator.
function TModuleCreator.NewImplSource(const ModuleIdent, FormIdent, AncestorIdent: String): IOTAFile; begin Result := TModuleCreatorFile.Create(FProjectWizardInfo, FAdditionalModule); end;
The NewIntfSource
method is where we would return a IOTAFile
interface to create a C++ interface header file. For our example we don’t need this so will return Nil.
function TModuleCreator.NewIntfSource(const ModuleIdent, FormIdent, AncestorIdent: String): IOTAFile; begin Result := Nil; end;
TModuleCreatorFile
The implementation of the file creator is essentially the same as that of the project creator however I’ve implemented the ability to expand “macros” in the templates to replace for example $MODULENAME$
with the name of the module.
The below method is a simple constructor to allow use to pass the project wizard information and the specific type of module being created to the GetSource
method.
constructor TModuleCreatorFile.Create(ProjectWizardInfo : TProjectWizardInfo; AdditionalModule : TAdditionalModule); begin FProjectWizardInfo := ProjectWizardInfo; FAdditionalModule := AdditionalModule; end;
The GetAge
method returns -1 to signify that this is a new unsaved file.
function TModuleCreatorFile.GetAge: TDateTime; begin Result := -1; end;
The GetSource
method is where we return the source code for each module through the use of the constant array defined earlier and the AdditionalModule
parameter passed to the class’s constructor.
function TModuleCreatorFile.GetSource: string; Const WizardMenu : Array[False..True] Of String = ('', ', IOTAMenuWizard'); ResourceString strResourceMsg = 'The OTA Module Template ''%s'' was not found.'; Var Res: TResourceStream; {$IFDEF D2009} strTemp: AnsiString; {$ENDIF} begin Res := TResourceStream.Create(HInstance, strProjectTemplate[FAdditionalModule].FResourceName, RT_RCDATA); Try If Res.Size = 0 Then Raise Exception.CreateFmt(strResourceMsg, [strProjectTemplate[FAdditionalModule].FResourceName]); {$IFNDEF D2009} SetLength(Result, Res.Size); Res.ReadBuffer(Result[1], Res.Size); {$ELSE} SetLength(strTemp, Res.Size); Res.ReadBuffer(strTemp[1], Res.Size); Result := String(strTemp); {$ENDIF} Finally Res.Free; End; Result := ExpandMacro(Result, '$MODULENAME$', ChangeFileExt(strProjectTemplate[FAdditionalModule].FModuleName, '')); Result := ExpandMacro(Result, '$USESCLAUSE$', GetUsesClauseCode); Result := ExpandMacro(Result, '$VARIABLEDECL$', GetVariableDeclCode); Result := ExpandMacro(Result, '$INITIALISEWIZARD$', GetInitialiseWizardCode); Result := ExpandMacro(Result, '$FINALISEWIZARD$', GetFinaliseWizardCode); Result := ExpandMacro(Result, '$WIZARDNAME$', FProjectWizardInfo.FWizardName); Result := ExpandMacro(Result, '$WIZARDIDSTRING$', FProjectWizardInfo.FWizardIDString); Result := ExpandMacro(Result, '$WIZARDMENUTEXT$', FProjectWizardInfo.FWizardMenuText); Result := ExpandMacro(Result, '$AUTHOR$', FProjectWizardInfo.FWizardAuthor); Result := ExpandMacro(Result, '$WIZARDDESCRIPTION$', FProjectWizardInfo.FWizardDescription); Result := ExpandMacro(Result, '$WIZARDMENUREQUIRED$', WizardMenu[FProjectWizardInfo.FWizardMenu]); end;
This next function simply allow you to substitute a macro name in the given text with some other text and have this returned by the function.
function TModuleCreatorFile.ExpandMacro(strText, strMacroName, strReplaceText: String): String; Var iPos : Integer; begin iPos := Pos(LowerCase(strMacroName), LowerCase(strText)); Result := strText; While iPos > 0 Do Begin Result := Copy(strText, 1, iPos - 1) + strReplaceText + Copy(strText, iPos + Length(strMacroName), Length(strText) - iPos + 1 - Length(strMacroName)); iPos := Pos(LowerCase(strMacroName), LowerCase(Result)); End; end;
The below function returns a string of code that needs to be inserted into the Finalization section of the main unit to remove the selected wizards from the IDE.
function TModuleCreatorFile.GetFinaliseWizardCode: String; begin If amKeyboardBindingInterface In FProjectWizardInfo.FAdditionalModules Then Result := Result + ' // Remove Keyboard Binding Interface'#13#10 + ' If iKeyBindingIndex > iWizardFailState Then'#13#10 + ' (BorlandIDEServices As IOTAKeyboardServices).RemoveKeyboardBinding(iKeyBindingIndex);'#13#10; If amIDENotifierInterface In FProjectWizardInfo.FAdditionalModules Then Result := Result + ' // Remove IDE Notifier Interface'#13#10 + ' If iIDENotfierIndex > iWizardFailState Then'#13#10 + ' (BorlandIDEServices As IOTAServices).RemoveNotifier(iIDENotfierIndex);'#13#10; If amCompilerNotifierInterface In FProjectWizardInfo.FAdditionalModules Then Result := Result + ' {$IFDEF D2010}'#13#10 + ' // Remove Compiler Notifier Interface'#13#10 + ' If iCompilerIndex <> iWizardFailState Then'#13#10 + ' (BorlandIDEServices As IOTACompileServices).RemoveNotifier(iCompilerIndex);'#13#10 + ' {$ENDIF}'#13#10; If amEditorNotifierInterface In FProjectWizardInfo.FAdditionalModules Then Result := Result + ' {$IFDEF D2005}'#13#10 + ' // Remove Editor Notifier Interface'#13#10 + ' If iEditorIndex <> iWizardFailState Then'#13#10 + ' (BorlandIDEServices As IOTAEditorServices).RemoveNotifier(iEditorIndex);'#13#10 + ' {$ENDIF}'#13#10; If amRepositoryWizardInterface In FProjectWizardInfo.FAdditionalModules Then Result := Result + ' // Remove Repository Wizard Interface'#13#10 + ' If iRepositoryWizardIndex <> iWizardFailState Then'#13#10 + ' (BorlandIDEServices As IOTAWizardServices).RemoveWizard(iRepositoryWizardIndex);'#13#10; end;
The below function returns a string of code that needs to be inserted into the Initialization section of the main unit to create the selected wizards in the IDE.
function TModuleCreatorFile.GetInitialiseWizardCode: String; begin If amKeyboardBindingInterface In FProjectWizardInfo.FAdditionalModules Then Result := Result + ' // Create Keyboard Binding Interface'#13#10 + ' iKeyBindingIndex := (BorlandIDEServices As IOTAKeyboardServices).AddKeyboardBinding('#13#10 + ' TKeybindingTemplate.Create);'#13#10; If amIDENotifierInterface In FProjectWizardInfo.FAdditionalModules Then Result := Result + ' // Create IDE Notifier Interface'#13#10 + ' iIDENotfierIndex := (BorlandIDEServices As IOTAServices).AddNotifier('#13#10 + ' TIDENotifierTemplate.Create);'#13#10; If amCompilerNotifierInterface In FProjectWizardInfo.FAdditionalModules Then Result := Result + ' {$IFDEF D2010}'#13#10 + ' // Create Compiler Notifier Interface'#13#10 + ' iCompilerIndex := (BorlandIDEServices As IOTACompileServices).AddNotifier('#13#10 + ' TCompilerNotifier.Create);'#13#10 + ' {$ENDIF}'#13#10; If amEditorNotifierInterface In FProjectWizardInfo.FAdditionalModules Then Result := Result + ' {$IFDEF D2005}'#13#10 + ' // Create Editor Notifier Interface'#13#10 + ' iEditorIndex := (BorlandIDEServices As IOTAEditorServices).AddNotifier('#13#10 + ' TEditorNotifier.Create);'#13#10 + ' {$ENDIF}'#13#10; If amRepositoryWizardInterface In FProjectWizardInfo.FAdditionalModules Then Result := Result + ' // Create Project Repository Interface'#13#10 + ' iRepositoryWizardIndex := (BorlandIDEServices As IOTAWizardServices).AddWizard('#13#10 + ' TRepositoryWizardInterface.Create);'#13#10; end;
The below function returns a string of code that needs to be inserted into the Uses clause of the main unit to allow access to the wizard interface definitions.
function TModuleCreatorFile.GetUsesClauseCode: String; begin If amKeyboardBindingInterface In FProjectWizardInfo.FAdditionalModules Then Result := Result + ' KeyboardBindingInterface,'#13#10; If amIDENotifierInterface In FProjectWizardInfo.FAdditionalModules Then Result := Result + ' IDENotifierInterface,'#13#10; If amCompilerNotifierInterface In FProjectWizardInfo.FAdditionalModules Then Result := Result + ' CompilerNotifierInterface,'#13#10; If amEditorNotifierInterface In FProjectWizardInfo.FAdditionalModules Then Result := Result + ' EditorNotifierInterface,'#13#10; If amRepositoryWizardInterface In FProjectWizardInfo.FAdditionalModules Then Result := Result + ' RepositoryWizardInterface,'#13#10; end;
The below function returns a a string of code that needs to be inserted into the variable declaration section of the main unit to hold the indexes of the wizard created.
function TModuleCreatorFile.GetVariableDeclCode: String; begin If amKeyboardBindingInterface In FProjectWizardInfo.FAdditionalModules Then Result := Result + ' iKeyBindingIndex : Integer = iWizardFailState;'#13#10; If amIDENotifierInterface In FProjectWizardInfo.FAdditionalModules Then Result := Result + ' iIDENotfierIndex : Integer = iWizardFailState;'#13#10; If amCompilerNotifierInterface In FProjectWizardInfo.FAdditionalModules Then Result := Result + ' {$IFDEF D2010}'#13#10 + ' iCompilerIndex : Integer = iWizardFailState;'#13#10 + ' {$ENDIF}'#13#10; If amEditorNotifierInterface In FProjectWizardInfo.FAdditionalModules Then Result := Result + ' {$IFDEF D0006}'#13#10 + ' iEditorIndex : Integer = iWizardFailState;'#13#10 + ' {$ENDIF}'#13#10; If amRepositoryWizardInterface In FProjectWizardInfo.FAdditionalModules Then Result := Result + ' iRepositoryWizardIndex : Integer = iWizardFailState;'#13#10; end;
Updates to the Project Creator
Finally, we modify the New DefaultProjectModule
code to iterate through all the different modules types creating those that have been selected.
{$IFDEF D0005} procedure TProjectCreator.NewDefaultProjectModule(const Project: IOTAProject); Var M: TModuleCreator; iModule: TAdditionalModule; begin For iModule := Low(TAdditionalModule) To High(TAdditionalModule) Do If iModule In FProjectWizardInfo.FAdditionalModules Then Begin M := TModuleCreator.Create(Project, FProjectWizardInfo, iModule); (BorlandIDEServices As IOTAModuleServices).CreateModule(M); End; end; {$ENDIF}
I hope this is straight forward. The code for this chapter can be downloaded here (OTAChapter14.zip). Enjoy 🙂
regards
Dave 🙂