Erik's Open Tools API FAQ and Resources for D5/D6 |
|
axsoft
版主 發表:681 回覆:1056 積分:969 註冊:2002-03-13 發送簡訊給我 |
Erik's Open Tools API FAQ and Resources for Delphi5/6資料來源:http://www.gexperts.org/contact.html If you have additions or corrections, please contact me. But, please do not send me questions about how to use the Open Tools API - instead, use the resources listed below. What is the Open Tools API? The Open Tools API (OTA) is a set of interfaces that allow developers to add features to the Delphi and C Builder IDEs. These additions are called wizards or experts. Wizards can use the OTA interfaces to modify the IDE, obtain information about the IDE's state, and receive notification of important events. To create wizards, you should first get the Professional or Enterprise edition of Delphi or C Builder, since these versions include the interface definitions in ToolsAPI.pas that will make your programming easier. Why isn't the Open Tools API documented by Borland? It is documented! Starting with C Builder 6 and Delphi 6, the OTA is fully documented. Open the *iota.hlp file and look at the index there for details. In the past, Borland has said that they want the freedom to change the Open Tools API interfaces as they need to, and that documentation would give users the impression that the interfaces will be stable. But, I think the real reason it is undocumented is that few people inside Borland use the interfaces and understand them fully. Also, the resources simply weren't previously available to draft the documentation. Where can I get help with my Open Tools API questions?function GetCurrentProject: IOTAProject; var Services: IOTAModuleServices; Module: IOTAModule; Project: IOTAProject; ProjectGroup: IOTAProjectGroup; MultipleProjects: Boolean; I: Integer; begin Result := nil; MultipleProjects := False; Services := BorlandIDEServices as IOTAModuleServices; for I := 0 to Services.ModuleCount - 1 do begin Module := Services.Modules[I]; if Module.QueryInterface(IOTAProjectGroup, ProjectGroup) = S_OK then begin Result := ProjectGroup.ActiveProject; Exit; end else if Module.QueryInterface(IOTAProject, Project) = S_OK then begin if Result = nil then // Found the first project, so save it Result := Project else MultipleProjects := True; // It doesn't look good, but keep searching for a project group end; end; if MultipleProjects then Result := nil; end;How do I obtain the current project group interface? You need to iterate through all of the modules to find the one that implements IOTAProjectGroup: function GetCurrentProjectGroup: IOTAProjectGroup; var IModuleServices: IOTAModuleServices; IModule: IOTAModule; IProjectGroup: IOTAProjectGroup; i: Integer; begin Result := nil; IModuleServices := BorlandIDEServices as IOTAModuleServices; for i := 0 to IModuleServices.ModuleCount - 1 do begin IModule := IModuleServices.Modules[i]; if IModule.QueryInterface(IOTAProjectGroup, IProjectGroup) = S_OK then begin Result := IProjectGroup; Break; end; end; end;How can I obtain the IOTAProjectResource interface for a given project? function GetProjectResource(Project: IOTAProject): IOTAProjectResource; var i: Integer; IEditor: IOTAEditor; begin Result := nil; for i:= 0 to (Project.GetModuleFileCount - 1) do begin IEditor := Project.GetModuleFileEditor(i); if Supports(IEditor, IOTAProjectResource, Result) then Break; end; end;How can I get a list of all installed components? See IOTAPackageServices.GetComponentName in ToolsAPI.pas. Is there any OTA support for creating type libraries or controlling the type library editor? If you look at IOTATypeLibEditor and IOTATypeLibModule, you'll see there might be internal plans to add some support, but it isn't implemented yet. Is there any OTA support for parsing a source file or obtaining Code Insight information? This is a common request, but the Open Tools API does not expose unit structure details or Code Insight information such as method parameter lists, symbol declaration locations, class members, and symbol table information. As a result, you will need to parse the source yourself or use an existing language parser such as those at Torry's Delphi Pages (search for mwDelPar or mwPasPar). Should I compile my wizard as a DLL or a Package? Packages are easier to load and unload without restarting the IDE (and hence easier to debug), but they can create unit naming conflicts in the IDE. Conflicts happen when the name a wizard's unit matches the name of a unit in another loaded design-time package. In this case, both packages can not be loaded at the same time. The recommended workaround is to prefix all of your unit names with a "unique" prefix. GExperts, for example, uses "GX_" as the name prefix for its units. Why does my wizard have to dynamically link to the VCL/DesignIde packages? If you want access to a useful BorlandIDEServices global variable from ToolsAPI.pas, you must compile your wizard linking to the VCL (Delphi 4/5) or DesignIde (Delphi 6) package. See the Packages tab in the Project Options dialog for help on compiling with packages. How do I get a hold of the designer for a DataModule? The owner of the datamodule at design-time is a TCustomForm which has a Designer property. How do a I get a reference to an IOTAXxxx interface? In general, just search ToolsAPI.pas for a method that returns the interface type you are looking for. But, sometimes things are a little trickier, and you have to use QueryInterface or Supports to find what you want: Interface Obtained From INTAComponent IOTAComponent INTAFormEditor IOTAFormEditor IFormDesigner INTAFormEditor IOTAKeyboardDiagnostics BorlandIDEServices IOTAEditActions IOTAEditView How can I debug a DLL wizard? Exit your IDE Remove any registry entries which load the expert DLL into your IDE. Look in HKEY_CURRENT_USER\Software\Borland\Delphi\X.0\Experts. Start your IDE, and verify the expert is not loaded. Compile your expert DLL. In the project options, be sure to turn on debug information, stack frames, reference info, etc. Turn optimizations off. Re-register the DLL with the IDE by adding an entry to HKEY_CURRENT_USER\Software\Borland\Delphi\X.0\Experts. Select Run, Parameters from the IDE menu. Enter the IDE's executable as the host application for your DLL. Run the host application (F9), and another copy of your IDE should appear with the expert loaded. You can now debug the DLL as it were a normal program (watches, breakpoints, inspections, tooltip evaluation, etc.). Note that library debugging does not work well in Delphi 4 and BCB 4. Both will lockup fairly often when debugging DLLs and packages. Delphi 3, 5, and 6 are much more stable in this respect. How can I debug a package wizard? In the project options for your package, turn on debug information, stack frames, reference info, etc. Turn optimizations off. Uncheck your package in the Project Options Packages tab, if necessary. Build your package (don't install it). Select Run, Parameters from the IDE menu. Enter the IDE's executable as the host application for your package. Run the host application (F9), and another copy of your IDE should appear. In the second copy of the IDE, open up the Project Options and load your expert package into the IDE. You can now debug the package as it were a normal program (watches, breakpoints, inspections, tooltip evaluation, etc.). Note that library debugging does not work well in Delphi 4 and BCB 4. Both will lockup fairly often when debugging DLLs and packages. Delphi 3, 5, and 6 are much more stable in this respect. How do I implement a form notifier (IOTAFormNotifier)? Declare something like this: TMyFormNotifier = class(TNotifierObject, IOTANotifier, IOTAFormNotifier) protected procedure FormActivated; procedure FormSaving; procedure ComponentRenamed(ComponentHandle: TOTAHandle; const OldName, NewName: string); end;Implement all of the above methods, even if they are blank. Finally, add your notifier using IOTAModule.GetModuleFileEditor(0).AddNotifier. In Delphi 5, form notifiers won't actually fire notifications for BeforeSave or AfterSave (use a module notifier for this). It should fire the notifications in the declaration above, as well as Destroyed and Modified from IOTANotifier. How do I implement an IDE notifier (IOTAIDENotifier)? Create an object that implements all of the IOTAIDENotifier and descendent interface methods. Then register the notifier using IOTAServices.AddNotifier and watch for notifications from the IDE. Be sure to call IOTAServices.RemoveNotifier when you are done. Here is a Delphi 5/6 example IOTAIDENotifier. unit IdeNotifier; interface procedure Register; implementation uses SysUtils, TypInfo, ToolsApi; var NotifierIndex: Integer; type TIdeNotifier = class(TNotifierObject, IOTANotifier, IOTAIDENotifier) protected procedure AfterCompile(Succeeded: Boolean); procedure BeforeCompile(const Project: IOTAProject; var Cancel: Boolean); procedure FileNotification(NotifyCode: TOTAFileNotification; const FileName: string; var Cancel: Boolean); end; procedure Register; var Services: IOTAServices; begin Services := BorlandIDEServices as IOTAServices; Assert(Assigned(Services), 'IOTAServices not available'); NotifierIndex := Services.AddNotifier(TIdeNotifier.Create); end; procedure RemoveNotifier; var Services: IOTAServices; begin if NotifierIndex <> -1 then begin Services := BorlandIDEServices as IOTAServices; Assert(Assigned(Services), 'IOTAServices not available'); Services.RemoveNotifier(NotifierIndex); end; end; function MsgServices: IOTAMessageServices; begin Result := (BorlandIDEServices as IOTAMessageServices); Assert(Result <> nil, 'IOTAMessageServices not available'); end; procedure TIdeNotifier.AfterCompile(Succeeded: Boolean); begin MsgServices.AddTitleMessage('After Compile'); end; procedure TIdeNotifier.BeforeCompile(const Project: IOTAProject; var Cancel: Boolean); begin MsgServices.AddTitleMessage('Before Compile'); end; procedure TIdeNotifier.FileNotification(NotifyCode: TOTAFileNotification; const FileName: string; var Cancel: Boolean); begin MsgServices.AddTitleMessage(Format('%s: %s', [GetEnumName(TypeInfo(TOTAFIleNotification), Ord(NotifyCode)), FileName])); end; initialization finalization RemoveNotifier; end.How can I get notified when a source file has changed? Don't use IOTAEditorNotifier since, surprisingly, it never fires in Delphi 4/5. Instead, attach a module notifier using IOTAModule.AddNotifier and watch for IOTAModuleNotifier.Modified. In Delphi 6, IOTAEditorNotifier works as expected. How can I add a menu item to the IDE's main menu? Use INTAServices.GetMainMenu to obtain a reference to the IDE's TMainMenu component. Iterate through all of the top-level menu items and find the parent menu item you want to add a menu item to. Then, use MyMenuItem.Insert() to add the menu item. GExperts has an IDE menu item expert that shows all IDE main menu items and their names. You may also find this tutorial by Miha Remec useful. How can I keep my wizard's menu item shortcuts from disappearing? In Delphi 5/6, you should register a shortcut for your menu item using the keybinding interfaces. First, implement a btPartial type keyboard binding via IOTAKeyboardBinding. In the BindKeyboard callback, use the passed in IOTAKeyBindingServices reference to call AddKeyBinding for each menu item, passing in the menu item's name as the last parameter (the ill named "HotKey" in Delphi 5): AddKeyBinding([ShortCut(Ord('G'), [ssCtrl])], FCallback, nil, 0, '', 'MenuItem1'); In Delphi 4, you will need to wait a few seconds after the IDE starts and set the menu item's ShortCut property. A timer works well to create the delay. How can I paint the palette bitmap for a specific component? You need to use the LibIntf unit, which is unsupported and undocumented, but try: LibIntf.DelphiIDE.GetPaletteItem(GetClass('TButton')).Paint How do I implement a module creator (IOTAModuleCreator)? Descend from TInterfacedObject and implement all of the methods in IOTACreator and IOTAModuleCreator as follows: TGxModuleCreator = class(TInterfacedObject, IOTACreator, IOTAModuleCreator) Here is sample code for a Delphi 5/6 module creator that resides in the repository. unit GXModuleCreator; interface procedure Register; implementation uses SysUtils, Windows, {$IFDEF VER130} // Delphi 5 DsgnIntf, {$ELSE not VER_140} // Delphi 6 DesignIntf, {$ENDIF} ToolsApi, Dialogs; type TGxModuleCreatorWizard = class(TNotifierObject, IOTAWizard, IOTARepositoryWizard, IOTAFormWizard) public // IOTAWizard function GetIDString: string; function GetName: string; function GetState: TWizardState; procedure Execute; // IOTARepositoryWizard function GetAuthor: string; function GetComment: string; function GetPage: string; {$IFDEF VER130} // Delphi 5 function GetGlyph: HICON; {$ELSE not VER_140} // Delphi 6 function GetGlyph: Cardinal; {$ENDIF} end; TGxModuleCreator = class(TInterfacedObject, IOTACreator, IOTAModuleCreator) public // IOTACreator function GetCreatorType: string; function GetExisting: Boolean; function GetFileSystem: string; function GetOwner: IOTAModule; function GetUnnamed: Boolean; // 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); end; TGxSourceFile = class(TInterfacedObject, IOTAFile) private FSource: string; public function GetSource: string; function GetAge: TDateTime; constructor Create(const Source: string); end; procedure Register; begin RegisterPackageWizard(TGxModuleCreatorWizard.Create); end; { TGxModuleCreatorWizard } procedure TGxModuleCreatorWizard.Execute; begin (BorlandIDEServices as IOTAModuleServices).CreateModule(TGxModuleCreator.Create); end; function TGxModuleCreatorWizard.GetAuthor: string; begin Result := 'Erik.Berry'; end; function TGxModuleCreatorWizard.GetComment: string; begin Result := 'GX Module Creator'; end; {$IFDEF VER130} // Delphi 5 function TGxModuleCreatorWizard.GetGlyph: HICON; {$ELSE not VER_140} // Delphi 6 function TGxModuleCreatorWizard.GetGlyph: Cardinal; {$ENDIF} begin Result := 0; end; function TGxModuleCreatorWizard.GetIDString: string; begin Result := 'EB.GXModuleCreatorWizard'; end; function TGxModuleCreatorWizard.GetName: string; begin Result := 'GX ModuleCreator'; end; function TGxModuleCreatorWizard.GetPage: string; begin Result := 'New'; end; function TGxModuleCreatorWizard.GetState: TWizardState; begin Result := [wsEnabled]; end; { TGxModuleCreator } procedure TGxModuleCreator.FormCreated(const FormEditor: IOTAFormEditor); begin // Nothing end; function TGxModuleCreator.GetAncestorName: string; begin Result := 'Form'; end; function TGxModuleCreator.GetCreatorType: string; begin // Return sUnit or sText as appropriate Result := sForm; end; function TGxModuleCreator.GetExisting: Boolean; begin Result := False; end; function TGxModuleCreator.GetFileSystem: string; begin Result := ''; end; function TGxModuleCreator.GetFormName: string; begin Result := ''; end; function TGxModuleCreator.GetImplFileName: string; begin Result := ''; end; function TGxModuleCreator.GetIntfFileName: string; begin Result := ''; end; function TGxModuleCreator.GetMainForm: Boolean; begin Result := False; end; function TGxModuleCreator.GetOwner: IOTAModule; var ModuleServices: IOTAModuleServices; Module: IOTAModule; NewModule: IOTAModule; begin // You may prefer to return the project group's ActiveProject instead Result := nil; ModuleServices := (BorlandIDEServices as IOTAModuleServices); Module := ModuleServices.CurrentModule; if Module <> nil then begin if Module.QueryInterface(IOTAProject, NewModule) = S_OK then Result := NewModule {$IFDEF VER130} // Delphi 5 else if Module.GetOwnerCount > 0 then begin NewModule := Module.GetOwner(0); {$ELSE not VER_140} // Delphi 6 else if Module.OwnerModuleCount > 0 then begin NewModule := Module.OwnerModules[0]; {$ENDIF} if NewModule <> nil then if NewModule.QueryInterface(IOTAProject, Result) <> S_OK then Result := nil; end; end; end; function TGxModuleCreator.GetShowForm: Boolean; begin Result := True; end; function TGxModuleCreator.GetShowSource: Boolean; begin Result := True; end; function TGxModuleCreator.GetUnnamed: Boolean; begin Result := True; end; function TGxModuleCreator.NewFormFile(const FormIdent, AncestorIdent: string): IOTAFile; begin Result := nil; end; function TGxModuleCreator.NewImplSource(const ModuleIdent, FormIdent, AncestorIdent: string): IOTAFile; const sSource = 'unit %s;' #13#10 '// Created with TGxModuleCreator' #13#10 '' #13#10 'interface' #13#10 '' #13#10 'uses' #13#10 ' Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs;' #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' #13#10 '' #13#10 '{$R *.DFM}' #13#10 '' #13#10 'end.'; begin Result := TGxSourceFile.Create(Format(sSource, [ModuleIdent, FormIdent, AncestorIdent, FormIdent, FormIdent])); // or Result := nil; for the default unit end; function TGxModuleCreator.NewIntfSource(const ModuleIdent, FormIdent, AncestorIdent: string): IOTAFile; begin Result := nil; end; { TGxSourceFile } constructor TGxSourceFile.Create(const Source: string); begin FSource := Source; end; function TGxSourceFile.GetAge: TDateTime; begin Result := -1; end; function TGxSourceFile.GetSource: string; begin Result := FSource; end; end.How do I implement a project creator (IOTAProjectCreator)? Descend from TInterfacedObject and implement all of the methods in IOTACreator and IOTAProjectCreator as follows: TGxProjectCreator = class(TInterfacedObject, IOTACreator, IOTAProjectCreator) Here is sample code for a project creator that installs itself into the Help menu. unit ProjectCreator; interface procedure Register; implementation uses Windows, ToolsApi; type TProjectWizard = class(TNotifierObject, IOTAMenuWizard, IOTAWizard) public procedure Execute; function GetIDString: string; function GetMenuText: string; function GetName: string; function GetState: TWizardState; end; TGxProjectCreator = class(TInterfacedObject, IOTACreator, IOTAProjectCreator) public // IOTACreator function GetCreatorType: string; function GetExisting: Boolean; function GetFileSystem: string; function GetOwner: IOTAModule; function GetUnnamed: Boolean; // IOTAProjectCreator function GetFileName: string; function GetOptionFileName: string; function GetShowSource: Boolean; procedure NewDefaultModule; function NewOptionSource(const ProjectName: string): IOTAFile; procedure NewProjectResource(const Project: IOTAProject); function NewProjectSource(const ProjectName: string): IOTAFile; end; procedure Register; begin RegisterPackageWizard(TProjectWizard.Create); end; procedure TProjectWizard.Execute; begin (BorlandIDEServices as IOTAModuleServices).CreateModule(TGxProjectCreator.Create); end; function TProjectWizard.GetIDString: string; begin Result := 'GX.ProjectCreator'; end; function TProjectWizard.GetMenuText: string; begin Result := '&GX Project Creator'; end; function TProjectWizard.GetName: string; begin Result := 'GX Project Creator'; end; function TProjectWizard.GetState: TWizardState; begin Result := [wsEnabled]; end; { TGxProjectCreator } function TGxProjectCreator.GetCreatorType: string; begin Result := sApplication; // Create an application project end; function TGxProjectCreator.GetExisting: Boolean; begin Result := False; // Create a new project end; function TGxProjectCreator.GetFileName: string; begin Result := ''; // Default end; function TGxProjectCreator.GetFileSystem: string; begin Result := ''; // Default end; function TGxProjectCreator.GetOptionFileName: string; begin Result := ''; // Default end; function GetCurrentProjectGroup: IOTAProjectGroup; var IModuleServices: IOTAModuleServices; IModule: IOTAModule; IProjectGroup: IOTAProjectGroup; i: Integer; begin Result := nil; IModuleServices := BorlandIDEServices as IOTAModuleServices; for i := 0 to IModuleServices.ModuleCount - 1 do begin IModule := IModuleServices.Modules[i]; if IModule.QueryInterface(IOTAProjectGroup, IProjectGroup) = S_OK then begin Result := IProjectGroup; Break; end; end; end; function TGxProjectCreator.GetOwner: IOTAModule; begin Result := GetCurrentProjectGroup; // Owned by current project group end; function TGxProjectCreator.GetShowSource: Boolean; begin Result := True; // Show the source in the editor end; function TGxProjectCreator.GetUnnamed: Boolean; begin Result := True; // Project needs to be named/saved end; procedure TGxProjectCreator.NewDefaultModule; begin // No default modules are created end; function TGxProjectCreator.NewOptionSource(const ProjectName: string): IOTAFile; begin Result := nil; // For BCB only end; procedure TGxProjectCreator.NewProjectResource(const Project: IOTAProject); begin // No resources needed end; function TGxProjectCreator.NewProjectSource(const ProjectName: string): IOTAFile; begin // Create the default source code for a new application Result := nil; end; end.How can I add menu items to the code editor's popup menu? IOTAEditView.GetEditWindow.Form.FindComponent('EditorLocalMenu') will return the editor's TPopupMenu component that you can add to. Your added menu items will work best if you add them to the end of the popup menu and may not work at all if you associate an action with them. Note that you might want an IOTAEditorNotifier to determine when to add your new menu items to new editor windows. How can I iterate over all units/forms in a project? IOTAProject.ModuleCount can be used to iterate over all modules, and IOTAProject.GetModule returns a reference to IOTAModuleInfo which gives you the FileName, FormName, etc. How can I publish a property of type T[Custom]Form? It doesn't work very well, but you can try to publish the property normally. The problem is that only currently created forms will be shown in the property editor, and storing an internal reference to one of those forms will often cause AVs when the target form is later closed. As a workaround, you can create a custom property editor that uses IOTAProject as above to get the class names of all forms in the project and insert them into the dropdown list for the property editor. Then store the form reference internally as a class name string, and use something like GetClass and RegisterClass to map a class name to a class type. With the class type, you can create the form at runtime. If you are sure your target form will always exist at runtime, another option to map from classes to instances is to search the Screen.Forms array. How can I obtain the currently active form editor (IOTAFormEditor)? function GetActiveFormEditor: IOTAFormEditor; var Module: IOTAModule; Editor: IOTAEditor; i: Integer; begin Result := nil; Module := (BorlandIDEServices as IOTAModuleServices).CurrentModule; if Module <> nil then begin for i := 0 to Module.GetModuleFileCount - 1 do begin Editor := Module.GetModuleFileEditor(i); Editor.QueryInterface(IOTAFormEditor, Result); if Result <> nil then Break; end; end; end; How can I obtain the current form designer interface (IFormDesigner)? function GetActiveFormDesigner: IFormDesigner; var FormEditor: IOTAFormEditor; begin Result := nil; FormEditor := GetActiveFormEditor; if FormEditor <> nil then Result := (FormEditor as INTAFormEditor).FormDesigner; end;How can I get a form editor from a module interface? You should iterate through all of the ModuleFileEditors, to see which one implements IOTAFormEditor: function GetFormEditorFromModule(IModule: IOTAModule): IOTAFormEditor; var i: Integer; IEditor: IOTAEditor; begin Result := nil; if IModule = nil then Exit; for i := 0 to IModule.GetModuleFileCount - 1 do begin IEditor := IModule.GetModuleFileEditor(i); if Supports(IEditor, IOTAFormEditor, Result) then Break; end; end;Is there a way to determine if the user is editing a form or working in the code editor? You can't do this using only the new Open Tools API in Delphi 5, but you can use the old API to get this information from ToolServices.GetCurrentFile. Starting with Delphi 6, there is IOTAModule.GetCurrentEditor for this purpose. If you find these methods don't work for .h files in C Builder try IOTAEditorServices.TopBuffer.FileName. How can I tell when a module was removed from a project? The old OTA had an fnRemovedFromProject notification flag, but this doesn't exist in the new OTA. As a workaround, you can watch for changes to the project file using an IOTAEditorNotifier and the iterate over all modules to see which one might have been deleted, if any. How can I obtain the Name pro |
本站聲明 |
1. 本論壇為無營利行為之開放平台,所有文章都是由網友自行張貼,如牽涉到法律糾紛一切與本站無關。 2. 假如網友發表之內容涉及侵權,而損及您的利益,請立即通知版主刪除。 3. 請勿批評中華民國元首及政府或批評各政黨,是藍是綠本站無權干涉,但這裡不是政治性論壇! |