Menus and Toolsbars (Delphi)

Plugins usually need to have a way to add controls (i.e. menu items or toolbars) to the host in order to let the users access functionality. Hydra introduces a set of interfaces and components that provide you with this capability, thus allowing you to extend your host's main form easily. Support is provided for the most commonly used component sets: Standard VCL, ExpressBars and Toolbar2000.

In order to use either of these two third party controls, you will need to have obtained and installed them from the appropriate vendors: Developer Express for ExpressBars and Jordan Russell for ToolBar2000 - these products are not shipped with Hydra.

Also, please note that components that use ExpressBars and ToolBar2000 don't install automatically, so in order to use them in your projects, you have to compile and install them manually. You can find the corresponding packages in the directory with the Hydra sources, they are named Hydra_DX_D*.dpk and Hydra_TB2K_D*.dpk.

Hydra introduces six components to work with the host user interface. You can see them in the screenshot below.

There are six menu and toolbar controllers for the different component sets (VCL, ExpressBars and Toolbar2000).

These components are used in your host application to build menus and toolbars.

The Hydra module manager now has two properties: MenuController and ToolbarController. Above, the module manager is connected to components that support ExpressBars, but it could also be hooked up to similar components for ToolBar2000 or the standard VCL out-of-the-box.

Menu and toolbar controllers are components that implement interfaces which allow you to add menu items, toolbars, etc. without having to learn how to use different component sets. The interfaces they implement are very simple to use:

{ IHYToolbarController }
IHYToolbarController = interface(IHYUpdateableController)
             ['{3CD82C84-97EF-48DC-BD82-004B77973877}']
  function GetItems(Index : integer) : IHYToolbar;
  function GetCount : integer;
  function Insert(const aName : string; anIndex : integer) : IHYToolbar;
  function Add(const aName : string) : IHYToolbar;
  property Items[Index : integer] : IHYToolbar read GetItems; default;
  property Count : integer read GetCount;
end;

{ IHYMenuController }
IHYMenuController = interface(IHYUpdateableController)
             ['{EC0FC315-7AD9-4B2E-8CD0-17726319AA7E}']
  function GetItems : IHYMenuItem;
  property Items : IHYMenuItem read GetItems;
end;

which, in turn, provides access to more granular interfaces such as:

{ IHYMenuItem }
IHYMenuItem = interface(IHYControl)
['{CBDFFE7A-5F69-46C1-B0C7-687D41D2EC11}']
  function GetItems(Index: integer): IHYMenuItem;
  function GetCount: integer;
  function Insert(anIndex: integer; anAction: TBasicAction): IHYMenuItem; overload;
  function Insert(anIndex: integer; const aCaption: string): IHYMenuItem; overload;
  function Add(anAction: TBasicAction): IHYMenuItem; overload;
  function Add(const aCaption: string): IHYMenuItem; overload;
  function InsertMenu(anIndex: integer; const aCaption: string): IHYMenuItem;
  function AddMenu(const aCaption: string): IHYMenuItem;
  function InsertSeparator(anIndex: integer): IHYMenuItem;
  function AddSeparator: IHYMenuItem;
  procedure Delete(Index: integer);
  function FindMenuItem(const aCaption: string): IHYMenuItem;
  function MenuItemByCaption(const aCaption: string): IHYMenuItem;
  property Items[Index: integer]: IHYMenuItem read GetItems; default;
  property Count: integer read GetCount;
end;

{ IHYToolbar }
IHYToolbar = interface(IHYObjectReference)
['{0BEFBFB0-C4A0-492D-96C8-B102181A6F93}']
  function GetRow: integer;
  procedure SetRow(Value: integer);
  function GetLeft: integer;
  procedure SetLeft(Value: integer);
  function GetTop: integer;
  procedure SetTop(Value: integer);
  function GetDockable: boolean;
  function GetShowHint: boolean;
  procedure SetShowHint(Value: boolean);
  function GetItems(Index: integer): IHYToolbarItem;
  function GetCount: integer;
  function GetIsModifying: boolean;
  function Insert(anIndex: integer; anAction: TBasicAction): IHYToolbarItem;
  function Add(anAction: TBasicAction): IHYToolbarItem;
  procedure InsertSeparator(anIndex: integer);
  procedure AddSeparator;
  procedure BeginModify;
  procedure EndModify;
  property Items[Index: integer]: IHYToolbarItem read GetItems; default;
  property Count: integer read GetCount;
  property IsModifying: boolean read GetIsModifying;
  property Row: integer read GetRow write SetRow;
  property Left: integer read GetLeft write SetLeft;
  property Top: integer read GetTop write SetTop;
  property Dockable: boolean read GetDockable;
  property ShowHint: boolean read GetShowHint write SetShowHint;
end;

Control repositories

The fifth component in the palette is the "Plugin Control Repository", which you will use inside your visual and non-visual plugins.

The purpose of this component is to store the definition of all the menus and toolbars you want your plugins to create. When you double-click on the component, an editor will be displayed and you will be able to define your menu structure and toolbars.

When your plugin is loaded, all these controls will automatically be created, assuming you set the corresponding options in the MergeOptions property, as shown in the following screenshot:

Putting it all together: the UI sample

The "Actions" demo that comes with Hydra provides a good showcase for the new user interface functionality.

It includes three main hosts built with the different component sets we support (VCL, ExpressBars and Toolbar2000) and just one plugin, which is capable of working with all of them without being aware of the controls used in the host.

This is a great advantage, especially for non-visual components, as it does not force you to bind your DLLs to more BPLs than strictly necessary.

The next screenshots show the different hosts embedding the same simple editor plugin:

The provided plugin adds the Edit menu and the toolbar together with the Cut, Copy and Paste icons and, through the use of a simple event called OnAfterMergeMenuItems, it adds a couple of new menu items to the pre-existing File menu. This code is used in that event:

procedure TTextEditor.HYPluginControlsRepository1AfterMergeMenuItems(
  Sender: THYPluginControlsRepository; const aHost: IHYHost;
    const anUpdateID: TGUID);
var filemenu : IHYMenuItem;
begin
  // Here we do a little bit of manual customization since the Open and
  // Save items need to go under the File menu
  if (aHost.MenuController=NIL) then Exit;

  filemenu := aHost.MenuController.Items.MenuItemByCaption('File');
  filemenu.Insert(0, actOpen);
  filemenu.Insert(1, actSave);
  filemenu.InsertSeparator(2);
end;

The other plugin included in the UI demo does all the work of adding menus and toolbars through code instead.

While it's most likely that people will only add new menus and toolbars and won't need to use code like this, we wanted to demonstrate how flexible Hydra can be, if needed.

The following code shows how to build a menu and a toolbar from scratch:

procedure TMiniWebBrowser.HYVisualPluginSetHost(const Sender: IHYHostAware;
  const aHost: IHYHost);
var browsermenu : IHYMenuItem;
    browsertoolbar : IHYToolbar;
    subimages : IHYSubImageList;
begin
  { In this plugin we build the menu and the toolbar manually }
  { Menu }
  if (aHost.MenuController<>NIL) then
    with aHost.MenuController do try
      BeginUpdate(TextEditorUpdate);

      browsermenu := Items.InsertMenu(1, 'Browse');
      if Supports(browsermenu, IHYSubImageList, subimages)
        then subimages.Images := ImageList;

      browsermenu.Add(actHome);
      browsermenu.AddSeparator;
      browsermenu.Add(actBack);
      browsermenu.Add(actForward);
      browsermenu.AddSeparator;
      browsermenu.Add(actStop);
    finally
      EndUpdate(TextEditorUpdate);
    end;

  { Toolbar }
  if (aHost.ToolbarController<>NIL) then
    with aHost.ToolbarController do try
      BeginUpdate(TextEditorUpdate);

      browsertoolbar := Add('My New Bar');
      if Supports(browsertoolbar, IHYSubImageList, subimages)
        then subimages.Images := ImageList;

      browsertoolbar.BeginModify;
      try
        browsertoolbar.Row := 1;
        browsertoolbar.Left := 100;
        browsertoolbar.ShowHint := True;

        browsertoolbar.Add(actBack);
        browsertoolbar.Add(actForward);
        browsertoolbar.Add(actStop);
        browsertoolbar.Add(actRefresh);
        browsertoolbar.Add(actHome);
      finally
        browsertoolbar.EndModify;
      end;
    finally
      EndUpdate(TextEditorUpdate);
    end;
end;

Once we have created menus and toolbars, we also want to be sure that we can easily destroy them when the plugin gets unloaded. If you look at the code above, you will notice the BeginUpdate and EndUpdate blocks, which take a parameter of type GUID. This ensures that all the controls added to a menu or toolbar controller are collected in a list indexed by that ID.

We will then use this list to delete all these new controls in one shot like we do in the OnDestroy event of this plugin:

procedure TMiniWebBrowser.HYVisualPluginDestroy(Sender: TObject);
begin
  if (Host.MenuController <> nil) then
    Host.MenuController.DeleteUpdates(TextEditorUpdate);
  if (Host.ToolbarController <> nil) then
    Host.ToolbarController.DeleteUpdates(TextEditorUpdate);
  fHistoryList.Free;
end;

Actions support for .NET plugins

Both Visual and NonVisual plugins provide an ability to define a set of actions as well as toolbar and menu items that will automatically be loaded and displayed by a Delphi host application.

The Hydra Actions category in Visual Studio:

The Actions property allows you to access the Hydra Actions editor:

In this editor, you can define your actions and set their properties (like Caption, ImageIndex, UserData, etc) and event handlers:

The Images property allows you to link an image list to an action (as well as menus and toolbars). Shown in this screenshot is a Toolbars property which allows you define a set of toolbars and menus that will be displayed by the host:

Both menu and toolbar do not have separate properties (except for submenus, which can have a separate Caption) or events, they will take all the information from the action that is linked with the item.

The last property is the UseLegacyActions. This property can be used for backward compatibility with an old actions handling mechanism. Old mechanism will only pass a limited set of properties to a host application, and actions execution must be handled in the Plugin's ActionExecute event handler.

Note: UseLegacyActions is disabled by default, so if you are migrating from a previous version and you don't want to rewrite your actions handling code, you will need to set this property to true or your actions handler will not be executed by the host. Also, you will need to enable this function in case you have a host that was build with a version of Hydra older than Spring 2011.

Now that we have a set of actions and toolbar and menu items defined, we can put everything together and use the host from the previous chapter to see the result:

Summary

This article has provided a review of one of the exciting features provided by Hydra. The menu and toolbar support provides visual support and significantly reduces the coding needed. Also, removing the plugins' dependence on the menu/toolbar system used by the host is a very useful feature. Another point, which might not be obvious, is that code provided by the plugins for menu systems can be used in the host itself. Finally, we believe the menu/toolbar support Hydra offers is the best approach to customization available at the moment, especially with the ease in which plugins can modify the host's menu system.