Image of Navigational Map linked to Home / Contents / Search How to write Delphi Wizards

TAddInMenuListExpert
Image of Line Break

Let's move on to our fourth and final kind of Experts: esAddIn Experts. These Experts are quite different from the previous ones. We can no longer rely on the Delphi IDE itself to query us and obtain a meun text or repository icon. In fact, we must "build" and "handle" our interface by ourselves. This means that - apart from GetStyle, GetIDString and GetName - the usual TIExpert functions are useless for us now. However, in order to be able to compile without warnings that tell me an "instance of a class containing abstract method is created", we still need to override every function; if only to do and return nothing.

The actual interface part of an esAddIn Expert is provided by the TIMainMenuIntf class, that we can find in the TOOLINTF.PAS file:

type
  TIMainMenuIntf = class(TInterface)
  public
    function GetMenuItems: TIMenuItemIntf; virtual; stdcall; abstract;
    function FindMenuItem(const Name: string): TIMenuItemIntf; virtual; stdcall; abstract;
end;

The TIMainMenuIntf class represents the Delphi main menu. We can actually get a list of menu items by calling GetMenuItems (which returns the top level menus), and we can search for a specific menu item with FindMenuItem, as long as we know the exact VCL component name of the particular item (i.e. not the name of the menu as it appears in the menubar, but the name of the menu item component itself!)

Once we have a list of menuitems, or we have found one particular menu items, we have a much more powerful component in our hands: the TIMenuItemIntf - an Expert's interface to menu items, with which we can add our own menu item(s) into the Delphi menu system!

type
  TIMenuFlag =
    (mfInvalid,mfEnabled,mfVisible,mfChecked,mfBreak,mfBarBreak,mfRadioItem);
  TIMenuFlags = set of TIMenuFlag;

  TIMenuClickEvent = procedure (Sender: TIMenuItemIntf) of object;

  TIMenuItemIntf = class(TInterface)
  public
    function DestroyMenuItem: Boolean; virtual; stdcall; abstract;
    function GetIndex: Integer; virtual; stdcall; abstract;
    function GetItemCount: Integer; virtual; stdcall; abstract;
    function GetItem(Index: Integer): TIMenuItemIntf; virtual; stdcall; abstract;
    function GetName: string; virtual; stdcall; abstract;
    function GetParent: TIMenuItemIntf; virtual; stdcall; abstract;
    function GetCaption: string; virtual; stdcall; abstract;
    function SetCaption(const Caption: string): Boolean; virtual; stdcall; abstract;
    function GetShortCut: Integer; virtual; stdcall; abstract;
    function SetShortCut(ShortCut: Integer): Boolean; virtual; stdcall; abstract;
    function GetFlags: TIMenuFlags; virtual; stdcall; abstract;
    function SetFlags(Mask, Flags: TIMenuFlags): Boolean; virtual; stdcall; abstract;
    function GetGroupIndex: Integer; virtual; stdcall; abstract;
    function SetGroupIndex(GroupIndex: Integer): Boolean; virtual; stdcall; abstract;
    function GetHint: string; virtual; stdcall; abstract;
    function SetHint(Hint: string): Boolean; virtual; stdcall; abstract;
    function GetContext: Integer; virtual; stdcall; abstract;
    function SetContext(Context: Integer): Boolean; virtual; stdcall; abstract;
    function GetOnClick: TIMenuClickEvent; virtual; stdcall; abstract;
    function SetOnClick(Click: TIMenuClickEvent): Boolean; virtual; stdcall; abstract;
    function InsertItem(Index: Integer; Caption, Name, Hint: string; virtual; stdcall; abstract;
      ShortCut, Context, GroupIndex: Integer; Flags: TIMenuFlags;
      EventHandler: TIMenuClickEvent): TIMenuItemIntf; virtual; stdcall; abstract;
  end;

The TIMenuItemIntf class is created by Delphi. This is simply a virtual interface to an actual menu item found in the IDE. It is the responsibility of the client to destroy all menu items which it created. Failure to properly clean-up will result in unpredictable behaviour, according to the comments in the source code of the class TIMenuItemIntf.
Using functions that returns a TIMenuItemIntf should be done with care. Unless created by a particular add-in tool, we should not keep the menu items for long, since the underlying actual VCL TMenuItem may be destroyed without any notification (in which case we're holding a pointer to nowhere). In practice, this only pertains to Delphi created menus or menus created by other add-in tools. It is also the responsibility of the user to free these interfaces when finished. Any attempt to modify a menu item that was created by Delphi will fail.

The most important functions are DestroyMenuItem, which needs to be called whenever we get a menu item from Delphi (i.e. allocated by Delphi, in for example the GetParent or GetItem functions).
Any menu item can have submenus. The function GetItemCount will return the number of submenus. Using GetItem we can walk through the list of menu items (warning: GetItem is zero-based, so start by counting from 0 to GetItemCount - 1, otherwise you'll get an index out of bounds exception). All TIMenuItemIntfs we get from GetItem must be freed by calling DestroyMenuItem on them again.
To get the true VCL component name of a menu item, we need to call the GetName method. This function is important, since we need the actual name to be able to search for menu items in the main menu (with the FindMenuItem function). Actually, it seems that we would need a list of names first, before we can actually search for a unique one.
Any menu item has a menu parent, and we can get the parent menu item by calling GetParent (obviously). A parent menu is important, since we must use the parent to be able to install a menu item next to another (in practice this means that the parent gets another child).
Using the GetCaption and SetCaption methods we can get and set the actual captions of the menu items. This may be useful, but can be very confusing (although we can only modify menu captions that are not part of the Delphi IDE skeleton - i.e. we can modify the text for the Database Expert, but we cannot modify the File menus). Using GetShortCut and SetShortCut we can get and set the shortcuts for the individual menu items. Again, we can not really modify the pre-existing Delphi IDE menu shortcuts, but only the added ones. Other functions include GetFlags and SetFlags, to get and set the state of the menu item; GetGroupIndex and SetGroupIndex, to get and set the GroupIndex property of a TMenuItem (useful for specifying values for grouped radio menus); GetContext and SetContext to get and set the help context ID of a TMenuItem; and finally GetHint and SetHint that do not work at all (the IDE seems to simply ignore them at this time).
There is one more really important method left: InsertItem. This is the API that creates and inserts our new sub menu item into the menu of the Delphi IDE. The function takes a lot of arguments, so let's have another look:

function InsertItem(Index: Integer;
                    Caption, Name, Hint: String;
                    ShortCut, Context, GroupIndex: Integer;
                    Flags: TIMenuFlags;
                    EventHandler: TIMenuClickEvent): TIMenuItemIntf; virtual; stdcall; abstract;
The index is the place where the new menu item should be placed (in the list of the Parent's menu items). If the index is less than zero or equal or bigger then GetItemCount, then the new menu item is actually appended to the bottom of the list (since the list is zero-based).
The Caption is the text that we'll see in the menu, something like '&Dr.Bob''s Wizard' or some useful text. The Name is the name of the VCL menu item component. It's not clear whether or not this name should be actually the same as the component name that holds the menu item that we've just created. And in that case, we should probably need to use some unique component name as well. For now, I've used DrBobItem, which seems pretty unique (so far). The final string is a Hint, which is not used at this time by the IDE anyway, so I leave it blank for now. Then, we can add a ShortCut key, a help context and groupindex. As menuflags I always use enabled and visible, but we can pick from a set of them (see a previous listing). Finally, we need to assign an OnClick event that gets fired when the menu item for our expert is selected. This is the place where previously our Execute method would kick in. Only the Execute method is no longer of the required type, since we need a method of type TIMenuClickEvent here. In short, the call to InsertItem could be as follows:
  DrBobItem := Tools.InsertItem(ToolsTools.GetIndex+1,
                              '&Dr.Bob''s Expert',
                               'DrBobItem','',
                                ShortCut(Ord('D'),[ssCtrl]),0,0,
                               [mfEnabled, mfVisible], OnClick);
The last two methods of TIMenuItemIntf are the GetOnClick and SetOnClick methods, who can be used to get and set the OnClick method (useful in case we want to do something else based on a special condition).

With this additional information, it's time to add code to our AddIn Expert. What would be the best place (and time) to add our AddIn Expert to the Delphi IDE menu? Well, a constructor would seem a fine place (and time) to me. But TIExpert doesn't have a constructor! OK, so let's define one! And while we're at it, let's override the destructor as well to make sure we clean up the MenuItem that we'll create in the constructor in the first place:

type
  TBAddInExpert = class(TIExpert)
  public
    constructor Create; virtual;
    destructor Destroy; override;

    function GetStyle: TExpertStyle; override;
    function GetIDString: String; override;
    function GetName: String; override;
    function GetAuthor: String; override;
    function GetMenuText: string; override;
    function GetState: TExpertState; override;
    function GetGlyph: HICON; override;
    function GetComment: string; override;
    function GetPage: string; override;

    { Expert Action }
    procedure Execute; override;

  protected
    procedure OnClick(Sender: TIMenuItemIntf); virtual;

  private
    MenuItem: TIMenuItemIntf;
  end {TBAddInExpert};

TBAddInExpert
GetStyle:esAddIn
GetIDString:DrBob.TBAddInExpert
GetName:AddIn Wizard
GetAuthor (win32):Bob Swart (aka Dr.Bob)
GetMenuText: 
GetState:[]
GetGlyph:0
GetPage (win32): 
GetComment: 

The code for the Create constructor, Destroy destructor and OnClick event handler is as follows:

constructor TBAddInExpert.Create;
var
  Main: TIMainMenuIntf;
  ToolsTools: TIMenuItemIntf;
  Tools: TIMenuItemIntf;
begin
  inherited Create;
  MenuItem := nil;
  if ToolServices <> nil then
  begin
    Main := ToolServices.GetMainMenu;
    if Main <> nil then { we've got the main menu }
    try
      ToolsTools := Main.FindMenuItem('ToolsToolsItem');
      if ToolsTools <> nil then { we've got the suh-menuitem }
      try
        Tools := ToolsTools.GetParent;
        if Tools <> nil then { we've got the Tools menu }
        try
          MenuItem := Tools.InsertItem(ToolsTools.GetIndex+1,
                                      '&Dr.Bob''s Expert',
                                       'DrBob','',
                                        ShortCut(Ord('D'),[ssCtrl]),0,0,
                                       [mfEnabled, mfVisible], OnClick)
        finally
          Tools.DestroyMenuItem
        end
      finally
        ToolsTools.DestroyMenuItem
      end
    finally
      Main.Free
    end
  end
end {Create};

destructor TBAddInExpert.Destroy;
begin
  if MenuItem <> nil then MenuItem.DestroyMenuItem;
  inherited Destroy
end {Destroy};

procedure TBAddInExpert.OnClick(Sender: TIMenuItemIntf);
begin
    MessageDlg('Hello Nashville!', mtInformation, [mbOk], 0)
end {OnClick};
Note that I've looked for the menuitem called 'ViewPrjMgrItem', which is a rather funny looking name. How did I know what menuname to look for in the first place? Well, sit tight, because we're about to find out all names of all menu items in the Delphi 2.0x, 3 and C++Builder IDE menu!
After we've installed our first AddIn Expert (in the usual way), we can start Delphi up again and see our first AddIn Expert as part of the Tool menu:

Menu Names We've seen a generic but pretty useless esAddIn Expert so far. In order to write truly more useful experts, we need to do something special inside the OnClick method like show an interesting form in which a lot of things can happen. But first, let's dig a little bit deeper in the main menu of the Delphi IDE. Now that we have the power, let's use it to get a list of VCL names for the individual menu items, so we don't need to look for one if we need it. To do this, I've modified the Create constructor of the AddIn Expert (and called it the new AddInMenuList Expert) to walk through the menu items of the main menu and print their names on a file as follows:

constructor TAddInMenuListExpert.Create;
var Main: TIMainMenuIntf;
    MenuItems: TIMenuItemIntf;
    ToolsTools: TIMenuItemIntf;
    Tools: TIMenuItemIntf;
var i,j: Integer;
    f: System.Text;
begin
  inherited Create;
  if ToolServices <> nil then
  try
    Main := ToolServices.GetMainMenu;
    if Main <> nil then { we've got the main menu }
    try
      MenuItems := Main.GetMenuItems;
      if MenuItems <> nil then
      try
        System.Assign(f,'C:\MENUS.D3');
        System.Rewrite(f);
        writeln(f,MenuItems.GetName,' -',MenuItems.GetItemCount);
        for i:=0 to Pred(MenuItems.GetItemCount) do
        begin
          Tools := MenuItems.GetItem(i);
          if Tools <> nil then { we've got a sub-menu }
          try
            writeln(f,'  ',Tools.GetName);
            for j:=0 to Pred(Tools.GetItemCount) do
            begin
              ToolsTools := Tools.GetItem(j);
              if ToolsTools <> nil then { sub-sub-menu }
              try
                writeln(f,'    ',ToolsTools.GetName);
              finally
                ToolsTools.DestroyMenuItem
              end
            end
          finally
            Tools.DestroyMenuItem
          end
        end
      finally
        System.Close(f);
        MenuItems.DestroyMenuItem
      end
    finally
      Main.Free
    end
  except
    { HandleException }
  end
end {Create};

The resulting list is pretty impressive, and gives us a good idea of which VCL menu item component names are used (and can be used as argument to the FindMenuItem function of the MainMenu).

Delphi 2.0xC++BuilderDelphi 3
FileMenu FileNewItem
FileNewApplicationItem
FileNewFormItem
FileNewDataModuleItem
FileOpenItem
FileClosedFilesItem
FileSaveItem
FileSaveAsItem
FileSaveProjectAs
FileSaveAllItem
FileCloseItem
FileCloseAllItem
N6
FileUseUnitItem
FileAddItem
FileRemoveItem
FilePrintItem
FileExitItem
FileNewItem
FileNewApplicationItem
FileNewFormItem
FileNewDataModuleItem
FileNewUnitItem
N16
FileOpenItem
OpenProjectItem
FileClosedFilesItem
N17
FileSaveItem
FileSaveAsItem
FileSaveProjectAs
FileSaveAllItem
FileCloseItem
FileCloseAllItem
N6
FileUseUnitItem
FilePrintItem
FileExitItem
FileNewItem
FileNewApplicationItem
FileNewFormItem
FileNewDataModuleItem
FileOpenItem
FileClosedFilesItem
FileSaveItem
FileSaveAsItem
FileSaveProjectAs
FileSaveAllItem
FileCloseItem
FileCloseAllItem
N6
FileUseUnitItem
FileAddItem
FileRemoveItem
N8
FilePrintItem
FileExitItem
EditMenu EditUndoItem
EditRedoItem
N15
EditCutItem
EditCopyItem
EditPasteItem
EditDeleteItem
EditSelectAll
N14
EditAlignGridItem
EditFrontItem
EditBackItem
EditAlignItem
EditSizeItem
EditScaleItem
EditTabOrderItem
CreationOrderItem
EditLockControlsItem
N5
EditObjectItem
EditUndoItem
EditRedoItem
N15
EditCutItem
EditCopyItem
EditPasteItem
EditDeleteItem
EditSelectAll
N14
EditAlignGridItem
EditFrontItem
EditBackItem
EditAlignItem
EditSizeItem
EditScaleItem
EditTabOrderItem
CreationOrderItem
EditLockControlsItem
N5
EditObjectItem
EditUndoItem
EditRedoItem
N15
EditCutItem
EditCopyItem
EditPasteItem
EditDeleteItem
EditSelectAll
N14
EditAlignGridItem
EditFrontItem
EditBackItem
EditAlignItem
EditSizeItem
EditScaleItem
EditTabOrderItem
CreationOrderItem
EditLockControlsItem
N17
EditAddToInterfaceItem
SearchMenu SearchFindItem
SearchReplaceItem
SearchAgainItem
SearchIncrementalItem
SearchGoToItem
SearchCompErrItem
SearchFindErrItem
SearchSymbolItem
SearchFindItem
SearchReplaceItem
SearchAgainItem
SearchIncrementalItem
SearchGoToItem
SearchCompErrItem
SearchFindErrItem
SearchFindItem
SearchFileFindItem
SearchReplaceItem
SearchAgainItem
SearchIncrementalItem
SearchGoToItem
SearchCompErrItem
SearchFindErrItem
SearchSymbolItem
ViewsMenu ViewPrjMgrItem
ViewPrjSourceItem
ViewObjInspItem
ViewAlignItem
ViewBrowserItem
ViewBreakpointsItem
ViewCallStackItem
ViewWatchItem
ViewThreadsItem
ViewCpuItem
ViewCompListItem
ViewWindowListItem
N1
ViewToggleFormItem
ViewUnitItem
ViewFormItem
N3
ViewNewEditorItem
N2
ViewSpeedBarItem
ViewPaletteItem
ViewSwapSourceFormItem
ViewPrjMgrItem
ViewPrjSourceItem
ViewMakeFileItem
ViewObjInspItem
ViewAlignItem
ViewCompListItem
ViewWindowListItem
N18
ViewCallStackItem
ViewThreadsItem
ViewCpuItem
ViewBreakpointsItem
ViewWatchItem
N1
ViewToggleFormItem
ViewUnitItem
ViewFormItem
N3
ViewNewEditorItem
N2
ViewSpeedBarItem
ViewPaletteItem
ViewSwapSourceFormItem
ViewPrjMgrItem
ViewPrjSourceItem
ViewObjInspItem
ViewAlignItem
ViewBrowserItem
ViewBreakpointsItem
ViewCallStackItem
ViewWatchItem
ViewThreadsItem
ViewModulesItem
ViewCpuItem
ViewCompListItem
ViewWindowListItem
N1
ViewToggleFormItem
ViewUnitItem
ViewFormItem
ViewTypeLibraryItem
N3
ViewNewEditorItem
N2
ViewSpeedBarItem
ViewPaletteItem
ViewSwapSourceFormItem
ProjectMenu ProjectAddItem
ProjectRemoveItem
ProjectAddRepositoryItem
N10
ProjectCompileItem
ProjectBuildItem
ProjectSyntaxItem
ProjectInformationItem
N11
ProjectOptionsItem
ProjectAddItem
ProjectRemoveItem
ProjectAddRepositoryItem
N10
ProjectCompileUnitItem
ProjectMakeItem
ProjectBuildItem
N11
ProjectInformationItem
ProjectAddItem
ProjectRemoveItem
ImportTypeLibraryItem
ProjectAddRepositoryItem
N10
ProjectCompileItem
ProjectBuildItem
ProjectSyntaxItem
ProjectInformationItem
N5
ProjectDepOptItem
ProjectDeployItem
N11
ProjectOptionsItem
RunMenu RunRunItem
RunParametersItem
N4
RunStepOverItem
RunTraceIntoItem
RunTraceToSourceItem
RunGotoCursorItem
RunShowCSIPItem
RunPauseItem
RunResetItem
RunAddWatchItem
RunAddBreakItem
RunEvalModItem
RunRunItem
RunParametersItem
N4
RunStepOverItem
RunTraceIntoItem
RunTraceToSourceItem
RunGotoCursorItem
RunShowCSIPItem
RunPauseItem
RunResetItem
RunInspectItem
RunEvalModItem
N19
RunAddWatchItem
RunAddBreakItem
RunRunItem
RunParametersItem
RunRegisterComItem
RunUnregisterComItem
N4
RunStepOverItem
RunTraceIntoItem
RunTraceToSourceItem
RunGotoCursorItem
RunShowCSIPItem
RunPauseItem
RunResetItem
RunAddWatchItem
RunAddBreakItem
RunEvalModItem
ComponentMenu ComponentNewItem
ComponentInstallItem
N7
ComponentOpenLibraryItem
ComponentRebuildItem
N8
ComponentPaletteItem
ComponentNewItem
ComponentInstallItem
N7
ComponentOpenLibraryItem
ComponentRebuildItem
N8
ComponentPaletteItem
ComponentNewItem
AddtoPackage1
ComponentImportAXCItem
N16
ComponentInstallCompositeItem
N7
InstallPackagesItem
ComponentPaletteItem
DatabaseMenu Borland_DbExplorerMenu
Borland_FormExpertMenu
Borland_DbExplorerMenu
Borland_FormExpertMenu
Borland_DbExplorerMenu
Borland_FormExpertMenu
ToolsMenu ToolsOptionsItem
ToolsGalleryItem
ToolsToolsItem
ToolsOptionsItem
ToolsGalleryItem
ToolsToolsItem
Options1 ProjectOptionsItem
ToolsOptionsItem
ToolsGalleryItem
HelpMenu HelpContentsItem
N13
HelpTopicSearchItem
HelpUsingHelpItem
HelpAPIItem
HelpAboutItem
HelpContentsItem
KeywordSearch
N20
ProgGuideItem
VclRefItem
RtlRefItem
N13 HelpAboutItem
HelpContentsItem
HelpTopicSearchItem
HelpWhatsNew
HelpGettingStarted
HelpUsingPascal
HelpDevelopingApps
HelpObjCompRef
N13
HelpBorlandPage
HelpDelphiPage
HelpProgramsPage
N18
HelpUsingHelpItem
HelpAPIItem
HelpAboutItem

If we want to write esAddIn Wizards that are compatible between Delphi 2.0x, C++Builder and Delphi 3, then we must make sure to pick an "entry" MenuItemName that exists in all three versions, so we can get a handle to an existing Menu item. Note that some menu items may not be available in all editions of Delphi 2.x, C++Builder or Delphi 3 (I used the Client/Server versions of all three to get the above table). Of course, you can always re-create this list yourself with the TBAddInMenu Wizard.


Image of Arrow linked to Next Article Image of Arrow linked to Next Article
Image of Line Break
[HOME] [TABLE OF CONTENTS] [SEARCH]