Introduction to the Delphi Open Tools API

In a recent article in this magazine I wrote about how you can add custom key bindings to Delphi's editor. In response, I received a number of questions about the mechanisms that permit Delphi developers to access something as internal as the code editor.

This mechanism is called the open tools API, or OTA for short. It's been around since the very earliest days of Delphi, and it’s one of the more powerful, and least exploited areas of Delphi development.
 
This is the first in a two part series on Delphi's open tools API. In this article I provide you with an overview of the OTA, and provide an example of a project that adds menu items to Delphi's menu. In the next article in this series, I will discuss a more complicated OTA implementation. Specifially, I will show you how to write a wizard that you can install into the Object Repository.

Overview of the Delphi Open Tools API

Since Delphi 1, the Delphi IDE has provided developers with access to some of its most internal features. It is through this mechanism that tools such as CodeSite, CodeRush, and GXperts, and the like, have been able to extend the capabilities of Delphi beyond those implemented by the Delphi team.
 
In the early days of Delphi, the open tools API was based on virtual, abstract base classes that provided templates for the features exposed through the OTA. In those days, you descended a custom class from one of these abstract classes, or one of their partially concrete descendants.
 
It was a little clumsy in the early days. Even adding a single new component to the component palette required a complete re-compilation of the installed visual component library (VCL). Nonetheless, it was a groundbreaking capability.
 
There have been many changes to the Delphi language over the years, and the open tools API has evolved along with it. Two of the major enhancements to Delphi that have had a profound influence on the open tools API was the introduction in Delphi 3 of packages and interfaces.
 
With design-time packages, it became possible to easily install OTA extensions without re-compiling the entire VCL. On the other hand, some of the OTA extensions didn't actually require additions to the VCL. Instead, some of them, such as Object Repository wizards, were added to Delphi by compiling your Wizard class in a DLL. Delphi loaded that DLL based on entries that you made to the Windows registry.
 
While packages made the installation of OTA classes easier, the introduction of interface support in the Delphi language had a much more profound influence on the architecture of the OTA. While interfaces were used sparingly when first introduced, they have grown exponentially more important in defining the application programming interface of the OTA. Indeed, the abstract classes of old are long deprecated, and the OTA is now entirely based on interfaces. And, with the release of the Galileo IDE (initially available in the now defunct C#Builder), the reliance of the OTA on pure interface definitions is complete.
 

A Brief Overview of Interfaces

Because they are so central to Delphi's open tools API, let's take a brief look at interfaces and their implementation. To begin with, an interface is a definition of public methods and properties. Interfaces serve as a sort of template for classes, defining the methods and properties that a class which implements the interface must support. In that sence, an interface defines a contract that a class must observe, without defining how that contract must be implemented.
 
One of the crucial features of an interface is that it provides for polymorphism without relying on common class ancestors. In other words, two classes that implement the same interface are assignment compatible with respect to that interface, regardless of what classes they descend from.
 
As far as the open tools API is concerned, these interfaces play two distinct roles. On the one hand, these interfaces define the collection of methods and properties that must be implemented in the classes that you create to implement your OTA features. (Well, that's not entirely accurate. The OTA also includes several interfaces, such as IOTAProjectWizard, that define no methods or properties. These interfaces are identifiers, and are used to signal to Delphi something about what roles the implementing object plays.)
 
The second role of these interfaces is to define the methods and properties that are available for your use in objects that the OTA supplies for you. Specifically, there are numerous objects that you can use to do such things as manipulate Delphi's menus, toolbars, and glyphs. Similarly, there are objects that the OTA provides so that you can interact with the debugger, the code editor, the Object Repository, the Message pane, code completion, and much more.
 
These interfaces are actually divided into two groups, the native tools API (NTA) and the open tools API (OTA). The NTA is used to access the Delphi IDE objects, such as the menus and toobars. The NTA is defined by a single interface, INTAServices.
 
By comparison, the OTA is a collection of interfaces that represent various facilities that the OTA makes available to you. These include IOTACodeCompletionServices, IOTADebuggerServices, and IOTAKeyBindingServices, to name just a few.
 
The objects provided you by the NTA and OTA are accessed through a single interface, IBorlandIDEServices. The object that implements this interface is assigned to a global variable named BorlandIDEServices that is declared in the ToolsAPI unit.
 
To access a service of the NTA or OTA you cast BorlandIDEServices to the appropriate interface. Actually, a better way to put this is that you should first ask whether or not BorlandIDEServices implements an object that implements the service you are interested in, and if so, then perform the cast.
 
The Supports function, defined in the SysUtils unit, is the preferred technique for performing this task. For example, consider the following code
 
var
 MessageService: IOTAMessageServices;
begin
 if Supports(
         BorlandIDEServices,
         IOTAMessageServices,
         MessageService) then
 begin
   //CustMessage is a class that
    //implements IOTACustomMessage
    MessServ.AddCustomMessage(CustMessage.Create);
 end;
 
Actually, this little code sample is a good representation of just how you approach the open tools API. Specifically, you use the Delphi provided object that implements the IOTAMessageServices interface to add a message to the Message pane (in this particular example). However, the method, AddCustomMessage, takes a single argument of the type IOTACustomMessage. That interfaces is one that you must implement in an object that you define.
 
In other words, Delphi provides some of the implementations of OTA (and NTA) interfaces, while you will supply additional objects that implement OTA interfaces, when necessary.

A Simple OTA Demonstration

In the following project I am going to show you how to add your own menu items to Dephi's main menu using both the OTA and NTA. One of these menu items is added by implementing the IOTAMenuWizard interface, which you can use to add a single menu item to Delphi's Help menu.
 
The second technique is more general, in that you can add one or more menu items to any of Delphi's menus. This second technique makes use of the NTA (and also requires greater care on your part, as you can seriously damage the Delphi IDE if you make mistakes).
 
To begin with, you start with a design time package. Use the following steps to begin the project
 
1.    From Delphi's main menu, select File | New| Package.
2.    Right click the newly created package in the Project Manager and select Options.
3.    From the Description page of the Project Options dialog box, set Usage options to Designtime only.
4.    Right-click the project in the Project Manager and select Add Reference. Add the designide.dcp package to the project. In Delphi XE, this project is located by default in C:\Program Files (x86)\Embarcadero\RAD Studio\8.0\lib\win32\release (omit the (x86) if you are using a 32-bit operating system).
 
Figure 1. Configuring the package to be a design time package
 
Next, we will add a unit in which we will define a class that implements the IOTAMenuWizard interface. To do this, use the following steps:
1.    Select File | New | Unit.
2.    Add the following units to the interface section uses clause: ToolsAPI, SysUtils, Menus, Dialogs.
3.    Add a class that descends from TNotifierObject and implements the IOTAWizard and IOTAMenuWizard interfaces. In addition, add a declaration of a no-parameter procedure named Register. The interface section of this unit should now look like the code that appears in Listing 1.
4.    Next, implement the methods of the TMenuDemo class and the Register procedure as shown in Listing 2.
 
interface
 
uses ToolsAPI, SysUtils, Menus, Dialogs
 
type
 TMenuDemo = class(
            TNotifierObject, IOTAWizard, IOTAMenuWizard
    )
    procedure MenuClick(Sender: TObject);
    constructor Create;
    { IOTAWizard }
    //Basic information about the wizard
    function GetIDString: string;
    function GetName: string;
    function GetState: TWizardState;
    procedure Execute;
    { IOTAMenuWizard }
    //Implement this interface to add an item
    //to the Help menu
    function GetMenuText: string;
 end;
 
procedure Register;
Listing 1
 
implementation
 
procedure Register;
begin
 RegisterPackageWizard(TMenuDemo.Create);
end;
 
{ TMenuDemo }
 
constructor TMenuDemo.Create;
var
 NTAServices: INTAServices;
 MainMenu: TMainMenu;
 Menu1, NewMenu: TMenuItem;
begin
 if Supports(
         BorlandIDEServices,
         INTAServices,
         NTAServices) then
 begin
    MainMenu := NTAServices.MainMenu;
    Menu1 := MainMenu.Items.Find('Project');
    if Menu1 <> nil then
    begin
      NewMenu := TMenuItem.Create(MainMenu);
      NewMenu.Caption := 'Custom Menu Item;
      NewMenu.OnClick := MenuClick;
      Menu1.Add(NewMenu);
    end
 end;end;
 
procedure TMenuDemo.Execute;
begin
 ShowMessage('Your OTA object is executing');
end;
 
function TMenuDemo.GetIDString: string;
begin
 Result := 'JDSI.MenuDemo';
end;
 
function TMenuDemo.GetMenuText: string;
begin
 Result := 'Select Me';
end;
 
function TMenuDemo.GetName: string;
begin
 Result := 'Simple Menu Demo';
end;
 
function TMenuDemo.GetState: TWizardState;
begin
 Result := [ToolsAPI.wsEnabled];
end;
 
procedure TMenuDemo.MenuClick(Sender: TObject);
begin
 ShowMessage('This is where your new menu ' +
               'item will do something cool');
end;
Listing 2.
 
The final step is to save your project and then install it. After saving your project, right-click the project in the Project Manager and select Install.
 
Once it has been installed, you will find two new menu items added to Delphi's menu. On the Help menu you will find the Select Me menu item created by the implementation of IOTAMenuWizard. Similarly, you will find the Custom Menu Item menu item at the bottom of Delphi's Project menu, as shown in Figure 2.
 
Figure 2. A custom menu item appears in Delphi's Project menu.
Conclusion
This article has given you a basic introduction to using Delphi's open tools API as well as its native tools API. In the next article in this series, we will take a look at a more complicated OTA project, one that adds a new custom wizard to Delphi's Object Repository.
Geef feedback:

CAPTCHA image
Vul de bovenstaande code hieronder in
Verzend Commentaar