delphitoolsapi

Add menu item to unit's tab context menu in Delphi IDE using ToolsAPI


I am looking to find out which services/interface I need to use to add an item to the right-click menu of a source file in the Delphi IDE.

For example, if I right-click on a unit's tab, it has items to "Close page", "Close all other pages", "Properties", etc. I want to add custom items to that menu, if possible.

I looked over the ToolsAPI unit but I have no clue where to begin. I assume there's an interface I can use to enumerate items and add items, but I dont know where to start looking.

If that's not possible, I'd settle for the code editor's context menu.

Maybe there's some samples online for this, but I'm still looking and have found none.

Any help appreciated.


Solution

  • Remy Lebeau has pointed you in exactly the right directions with his link to the GExperts guide.

    If you've not done this sort of stuff before, it can still be a bit of a performance to get started on writing your own IDE add-in, so I've set out below a minimal example of how to add an item to the code editor's pop-up menu.

    What you do, obviously, is to create a new package, add the unit below to it, and then install the package in the IDE. The call to Register in the unit does what's necessary to install the new item in the editor pop-up menu.

    Make sure that the code editor is open at the time you install the package. The reason is that, as you'll see, the code checks whether there is an active editor at the time. I've left how to ensure that the pop-up item gets added even if there is no code editor active at the time. Hint: if you look at the ToolsAPI.Pas unit for whichever version of Delphi you're using, you'll find that it includes various kinds of notifier, and you can use a notification from at least one of them to defer checking if there is an editor active until one is likely to be.

    Btw, the code adds the menu item to the context menu which pops up over the editor window itself rather than the active tab. Part of the fun with IDE add-ins is the fun of experimenting to see if you can get exactly what you want. I haven't tried it myself, but I doubt that adding the menu item to the context menu of one of the editor tabs would be that difficult - seeing as the Delphi IDE is a Delphi app, as you can see from the code below, you can use FindComponent (or just iterate over a Components collection) to find what you want. However, it is better, if you can, to locate things via the ToolsAPI interfaces. See Update below.

    interface
    
    uses
      Classes, Windows, Menus, Dialogs, ToolsAPI;
    
    type
    
       TIDEMenuItem = class(TNotifierObject, IOTAWizard, IOTAMenuWizard)
         function GetName: string;
         function GetIDString: string;
         function GetMenuText: string;
         function GetState: TWizardState;
         procedure Execute;
       end;
    
       TIDEMenuHandler = class(TObject)
         procedure HandleClick(Sender: TObject);
       end;
    
    procedure Register;
    
    implementation
    
    var
      MenuItem: TMenuItem;
      IDEMenuHandler: TIDEMenuHandler;
      EditorPopUpMenu : TPopUpMenu;
    
    procedure TIDEMenuItem.Execute;
    begin
      ShowMessage('Execute');
    end;
    
    function TIDEMenuItem.GetIDString: string;
    begin
      Result := 'IDEMenuItemID';
    end;
    
    function TIDEMenuItem.GetMenuText: string;
    begin
      Result := 'IDEMenuItemText';
    end;
    
    function TIDEMenuItem.GetName: string;
    begin
      Result := 'IDEMenuItemName';
    end;
    
    function TIDEMenuItem.GetState: TWizardState;
    begin
      Result := [wsEnabled];
    end;
    
    procedure TIDEMenuHandler.HandleClick(Sender: TObject);
    begin
      ShowMessage(TIDEMenuItem(Sender).GetName + ' Clicked');
    end;
    
    procedure AddIDEMenu;
    var
      NTAServices: INTAServices40;
      EditorServices: IOTAEditorServices;
      EditView: IOTAEditView;
    begin
      NTAServices := BorlandIDEServices as INTAServices40;
    
      EditorServices := BorlandIDEServices as IOTAEditorServices;
      EditView := EditorServices.TopView;
    
      if Assigned(EditView) then begin
        EditorPopUpMenu := TPopUpMenu(EditView.GetEditWindow.Form.FindComponent('EditorLocalMenu'));
        Assert(EditorPopUpMenu <>Nil);
    
        IDEMenuHandler := TIDEMenuHandler.Create;
        MenuItem := TMenuItem.Create(Nil);
        MenuItem.Caption := 'Added IDE editor menu item';
        MenuItem.OnClick := IDEMenuHandler.HandleClick;
        EditorPopUpMenu.Items.Add(MenuItem)
      end
      else
        ShowMessage('Code editor not active');
    end;
    
    procedure RemoveIDEMenu;
    begin
      if MenuItem <> Nil then begin
        EditorPopUpMenu.Items.Remove(MenuItem);
        FreeAndNil(MenuItem);
        IDEMenuHandler.Free;
      end;
    end;
    
    procedure Register;
    begin
      RegisterPackageWizard(TIDEMenuItem.Create);
      AddIDEMenu;
    end;
    
    initialization
    
    finalization
      RemoveIDEMenu;
    end.
    

    Update: The following code finds the TabControl of the editor window and adds the menu item to its context menu. However, note that it does not account for there being a second editor window open.

    procedure AddIDEMenu;
    var
      NTAServices: INTAServices40;
      EditorServices: IOTAEditorServices;
      EditView: IOTAEditView;
      TabControl : TTabControl;
    
      function FindTabControl(AComponent : TComponent) : TTabControl;
      var
        i : Integer;
      begin
        Result := Nil;
        if CompareText(AComponent.ClassName, 'TXTabControl') = 0 then begin
          Result := TTabControl(AComponent);
          exit;
        end
        else begin
          for i := 0 to AComponent.ComponentCount - 1 do begin
            if CompareText(AComponent.Components[i].ClassName, 'TXTabControl') = 0 then begin
              Result := TTabControl(AComponent.Components[i]);
              exit;
            end
            else begin
              Result := FindTabControl(AComponent.Components[i]);
              if Result <> Nil then
                exit;
            end;              
          end;
        end;
      end;
    
    begin
      NTAServices := BorlandIDEServices as INTAServices40;
    
      EditorServices := BorlandIDEServices as IOTAEditorServices;
      EditView := EditorServices.TopView;
    
      if Assigned(EditView) then begin
        TabControl := FindTabControl(EditView.GetEditWindow.Form);
        Assert(TabControl <> Nil, 'TabControl not found');
        EditorPopUpMenu := TabControl.PopupMenu;
        Assert(EditorPopUpMenu <> Nil, 'PopUP menu not found');
        //EditorPopUpMenu := TPopUpMenu(EditView.GetEditWindow.Form.FindComponent('EditorLocalMenu'));
        Assert(EditorPopUpMenu <>Nil);
    
        IDEMenuHandler := TIDEMenuHandler.Create;
        MenuItem := TMenuItem.Create(Nil);
        MenuItem.Caption := 'Added IDE editor menu item';
        MenuItem.OnClick := IDEMenuHandler.HandleClick;
        EditorPopUpMenu.Items.Add(MenuItem)
      end
      else
        ShowMessage('No editor active');
    end;