| Erik's Open Tools API FAQ |
How do I obtain the current project (IOTAProject) interface?Starting with Delphi 7, you can use the function GetActiveProject. For previous releases, you can iterate through all of the modules to find the project group and then get that group's active project:
function GetCurrentProject: IOTAProject;
var
ModServices: IOTAModuleServices;
Module: IOTAModule;
Project: IOTAProject;
ProjectGroup: IOTAProjectGroup;
i: Integer;
begin
Result := nil;
ModServices := BorlandIDEServices as IOTAModuleServices;
for i := 0 to ModServices.ModuleCount - 1 do
begin
Module := ModServices.Modules[i];
if Supports(Module, IOTAProjectGroup, ProjectGroup) then
begin
Result := ProjectGroup.ActiveProject;
Exit;
end
else if Supports(Module, IOTAProject, Project) then
begin // In the case of unbound packages, return the 1st
if Result = nil then
Result := Project;
end;
end;
end;
How do I obtain the current project group (IOTAProjectGroup) interface?Starting with Delphi 7, you can use the GetActiveProjectGroup function. For earlier releases, you can iterate through all of the modules to find the one that implements IOTAProjectGroup:
function GetCurrentProjectGroup: IOTAProjectGroup;
var
ModServices: IOTAModuleServices;
ProjectGroup: IOTAProjectGroup;
Module: IOTAModule;
i: Integer;
begin
Result := nil;
ModServices := BorlandIDEServices as IOTAModuleServices;
for i := 0 to ModServices.ModuleCount - 1 do
begin
Module := ModServices.Modules[i];
if Supports(Module, IOTAProjectGroup, ProjectGroup) then
begin
Result := ProjectGroup;
Break;
end;
end;
end;
How can I obtain the IOTAProjectResource interface for a given project?Note that there is a bug in Delphi 2005/2006 that may prevent this from working.
function GetProjectResource(Project: IOTAProject): IOTAProjectResource;
var
i: Integer;
Editor: IOTAEditor;
begin
Result := nil;
for i:= 0 to (Project.GetModuleFileCount - 1) do
begin
Editor := Project.GetModuleFileEditor(i);
if Supports(Editor, 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. For now those interfaces can only be used to see if a file is a type library or not. Apparently, COM provides some services to create type libraries.
Is there any OTA support for parsing a source file or obtaining Code Insight information?C#Builder, Delphi 8+, and BDS support obtaining the .NET CodeDom for C# and Delphi code using IOTACodeDomProvider, but older IDEs do 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, for examples). Starting with Delphi 7, you can implement your own code completion and parameter hints via the IOTACodeInsight* interfaces, but the source parsing is still up to you.
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 DesignIde package (in Deplhi 6+) or the VCL pcckage (in Delphi 4/5). 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 Supports or QueryInterface to find what you want:
How can I debug a DLL wizard?
How can I debug a package wizard?
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 notification callbacks from the IDE. Be sure to call IOTAServices.RemoveNotifier when you are done. Here is a Delphi 5/6 example IOTAIDENotifier.
How do I implement an editor notifier (IOTAEditorNotifier)?Declare something similar to this:
TTestEditorNotifier = class(TNotifierObject, IOTAEditorNotifier)
public
procedure Destroyed; override;
procedure ViewActivated(const View: IOTAEditView);
procedure ViewNotification(const View: IOTAEditView; Operation: TOperation);
end;
Overrride any of the other IOTANotifier methods you need and add the notifier using IOTASourceEditor.AddNotifier. In the Destroyed notification remove the notifier using IOTASourceEditor.RemoveNotifier and the index returned from AddNotifier. Here is a sample notifier package that works in BDS 2006.
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. Note that 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 can I get notified when a source file has changed?Starting in Delphi 6, IOTAEditorNotifier works for this. In Delphi 4/5 IOTAEditorNotifier never fires, so instead, attach a module notifier using IOTAModule.AddNotifier and watch for IOTAModuleNotifier.Modified.
How can I get notified when a form designer or component is selected?When a form is activated, IOTAFormNotifier.FormActivated fires. If you are willing to add a notifier to every open form, this event will provide you with direct notifications. If you would rather not install a notifier for every open form, try using IDesignNotification.SelectionChanged. You will need to register your IDesignNotification interface with the IDE by calling RegisterDesignNotification and check the active form in each SelectionChanged callback.
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. To handle selction of your menu item and associate an icon with it, will need to add an action to the main IDE action list, add an image to the IDE's image list, and set the action's ImageIndex and OnExecute properties. See INTAServices40 for the related methods. You may also find this tutorial by Miha Remec useful.
How can I add a shortcut to my main menu item?In Delphi 5+, you should register a shortcut for your menu item using the keybinding interfaces:
AddKeyBinding([ShortCut(Ord('G'), [ssCtrl])], Callback,
nil, 0, '', 'MyMenuItem');
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. Here is a sample keybinding unit that should work in Delphi 6 - Delphi 2007 to get you started. You will need to customize it to handle your menu item, as described above.
How can I paint the palette bitmap for a specific component?In Delphi 4/5 you need to use the LibIntf unit, which is unsupported and undocumented, but try: LibIntf.DelphiIDE.GetPaletteItem(GetClass('TButton')).Paint
In Delphi 6+ you can try to load the component bitmaps from the .bpl package resources manually (by name) or try using the ComponentDesigner unit similar to this:
var
PaletteItem: IPaletteItem;
PalettePaint: IPalettePaint;
begin
with ComponentDesigner.ActiveDesigner.Environment do
PaletteItem := GetPaletteItem(
TComponentClass(FindClass('TButton'))) as IPaletteItem;
PalettePaint := PaletteItem as IPalettePaint;
PalettePaint.Paint(ACanvas, X, Y);
How do I implement a module creator (IOTAModuleCreator/IOTAFormWizard)?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 6-2007 module creator that resides in the File, New, Other repository screen by implementing IOTARepositoryWizard and the other required interfaces.
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.
How can I save/load values to a project's desktop settings (.dsk) file?In the FileNotification method of an installed IOTAIDENotifier, check for the ofnProjectDesktopLoad and ofnProjectDesktopSave NotifyCode values. When you see one of those, you can save/load values from the file indicated by the FileName parameter using a class such as TIniFile.
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 does the native OTA deal with UNICODE strings in the editor?Starting with Delphi 8, the OTA will usually return UNICODE editor content as UTF-8 encoded strings and expect UTF-8 strings when writing into the editor. This means that for many common low-ASCII characters (the most common in the Latin alphabet), the encoding will not differ form the ASCII encoding, but for some other characters, such a German umlaut, the UTF-8 encoding will result in 2-bytes in the string for this character. You can convert from UTF-8 returned strings to ANSI using Utf8ToAnsi and convert from ANSI strings to UTF-8 for insertion into the editor using AnsiToUtf8.
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 do custom painting or drawing (colors, lines, etc.) in the IDE code editor?There isn't any OTA support for custom drawing in the editor, beyond writing your own syntax highlighter (see IOTAHighlighter). All of the attempts to hack custom drawing into the code editor involve low level techniques such as windows hooks, runtime VMT patching, package export patching, etc. You have to manually calculate the position of characters onscreen, so you need to know a lot about how the editor draws characters, what portions of the code are folded, how the IDE painting changes with certain fonts and with italics involved, etc. All of these methods are complex, cause extra flickering in the editor, are difficult to debug, can cause IDE instability, and can also slow the editor down enough that it becomes less usable on slower machines. 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 create a form that docks into the IDE like the Object Inspector?You need to descend from TDockableForm in the DesignIDE package's DockForm unit and perform some magic to register your dockable form with the IDE. Here is a docking form example should work in Delphi 7 - 2007 and shows how to declare a basic docking form. Compile the included package, install it into the IDE, and then look at the new menu item in the Help menu. Also, see Allen Bauer's article on the CodeGear web site for an overview and a Delphi 5 example.
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);
if Supports(Editor, IOTAFormEditor, Result) 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(Module: IOTAModule): IOTAFormEditor;
var
i: Integer;
Editor: IOTAEditor;
begin
Result := nil;
if Module = nil then
Exit;
for i := 0 to Module.GetModuleFileCount - 1 do
begin
Editor := Module.GetModuleFileEditor(i);
if Supports(Editor, 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?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. 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. 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 property of a form?If the form is part of a loaded project, use IOTAModuleInfo.GetFormName. If the form is not part of a loaded project, you can try IOTAFormEditor.GetRootComponent.GetIComponent.Name, but it isn't always reliable (it can cause AVs of the form is not visible). A more reliable method might be to force the form visible before calling GetRootComponent or you could manually scan the associated DFM stream for the form's Name property. You need to use I[Form]Designer.CreateMethod from the DsgnIntf/DesignIntf unit and then SetMethodProp from the TypInfo unit. Here is a simple Delphi 5/6 example that uses both of these functions to create and assign an OnClick handler for the current form.
How can I create a method and then assign an event handler at design-time?You need to use I[Form]Designer.CreateMethod from the DsgnIntf/DesignIntf unit and then SetMethodProp from the TypInfo unit. Here is a simple Delphi 5/6 example that uses both of these functions to create and assign an OnClick handler for the current form.
How can I force the code editor to show a specific file tab?You can either use IOTASourceEditor.Show or call IOTAActionServices.OpenFile and pass in the full path and filename of the file tab to activate.
How can I determine the filename of the binary/exe/dll/bpl/ocx/etc. generated by a compile or build?
Known Open Tools API BugsKnown bugs in the Delphi 2007 Open Tools API (most also apply to earlier releases):
Known bugs in the BDS 2006 Open Tools API (most also apply to earlier releases):
Known bugs in the Delphi 2005 Open Tools API:
Known bugs in the Delphi 7 Open Tools API (most also apply to Delphi/BCB 6):
Known bugs in the Delphi 6 Open Tools API (most also apply to Delphi/BCB 5):
Known bugs in the C++Builder 5.01 Open Tools API:
Known bugs in the Delphi 5.01 Open Tools API:
|
||||||||||||||||||||||||||||||||||
| Last Updated on Thursday, 01 October 2009 23:13 |
