Jump to content
bravesofts

identify whether a TMenuItem is a top-level menu bar item

Recommended Posts

In Delphi VCL, I'm trying to identify whether a TMenuItem is a top-level menu bar item (a MenuBarItem) as opposed to a regular submenu item.

I'm currently using this check:

if aItem.Count > 0 then

But this returns True for both top-level menu items (e.g., "File", "Edit" in the main menu bar) and submenu items that have their own children (like "Recent Files" under "File").

Is there a more reliable way—perhaps using TMenuItem properties or GetMenuItemInfo from the Windows API—to distinguish true menu bar items from submenu items with children?

i'm using this:

function IsMenuBarItem(aMenu: TMainMenu; aItem: TMenuItem): Boolean;
var
  LInfo: MENUITEMINFOW;
  LHMenu: HMenu;
  I: Integer;
begin
  Result := False;

  LHMenu := aMenu.Handle;

  ZeroMemory(@LInfo, SizeOf(LInfo));
  LInfo.cbSize := SizeOf(LInfo);
  LInfo.fMask := MIIM_SUBMENU;

  for I := 0 to GetMenuItemCount(LHMenu) - 1 do
  begin
    if GetMenuItemInfoW(LHMenu, I, True, LInfo) then
    begin
      if LInfo.hSubMenu = aItem.Handle then
      begin
        Result := True;
        Exit;
      end;
    end;
  end;
end;

is possible to avoid iterating all items in the main menu if i trying to determine whether a given TMenuItem is a menu bar item, using only information from the TMenuItem itself !

---

🧩 Context:

The reason I'm doing this is to work around the automatic access key (Alt + key) assignment that Windows applies when menu items don't have an & in their caption.

Even after setting:

TMainMenu.AutoHotkeys := maManual;

the issue persisted.

I'm working in a Right-to-Left (BiDiMode) environment with OwnerDraw := True, and the system still assigns hotkeys unless the & is explicitly added to the menu captions.

👉Hint: the TMainMenu is hosted by TToolBar and not the Form !

--

🛠️ MySolution: Add "&" to Non-MenuBar Items

Here's the utility I use to ensure all non-menu bar items have an & prefix:

procedure TdmUI.FixMenuCaptions(aMenu: TMainMenu);
var
  I: Integer;

  procedure FixItem(aItem: TMenuItem);
  var
    J: Integer;
  begin
    if (Pos('&', aItem.Caption) = 0) and
       (not aItem.IsLine) and
       (not IsMenuBarItem(aMenu, aItem)) then //or (aItem.Count > 0) or (aItem.Tag <> 2)

      aItem.Caption := '&' + aItem.Caption; // Add & if missing

    for J := 0 to aItem.Count - 1 do
      FixItem(aItem.Items[J]);
  end;

begin
  for I := 0 to aMenu.Items.Count - 1 do
    FixItem(aMenu.Items[I]);
end;

This approach above ensures that only submenu items get the &, preserving the expected behavior on the menu bar, especially in right-to-left and owner-drawn setups.

Edited by bravesofts

Share this post


Link to post

TMenuItem has got a Parent property of type TMenuItem. I haven't checked but I would assume that it is NIL for main menu items.

 

Edit: I was wrong. See online help here:

 

https://docwiki.embarcadero.com/Libraries/en/Vcl.Menus.TMenuItem.Parent

 

It's identical to the MainMenu's Items property. But that's just as good for detecting main menu items.

Edited by dummzeuch

Share this post


Link to post
2 minutes ago, Minox said:

You could use the "Tag" property to distinguish them.

I know about using the Tag property, but I'm aiming to write a clean, universal solution or design pattern that works reliably with any dropped TMainMenu, without relying on manual tagging or special-case logic. I want something that auto-detects whether a TMenuItem is a menu bar item based purely on its structure, so it can be reused across different forms and menus with minimal setup. 

Share this post


Link to post

Use two loops to start with so you iterate into sub menu items before calling FixItem(). Then FixItem() only gets submenu items and can be simplified.
 

  for I := 0 to aMenu.Items.Count - 1 do
    for k := 0 to aMenu.items[i].Count -1 do
       FixItem (aMenu.Items[I].items[k]);

 

Edited by Brian Evans

Share this post


Link to post
12 minutes ago, Brian Evans said:

Use two loops to start with so you iterate into sub menu items before calling FixItem(). Then FixItem() only gets submenu items and can be simplified.

procedure TdmUI.FixMenuCaptions(aMenu: TMainMenu);
var
  I, K: Integer;

  procedure FixItem(aItem: TMenuItem);
  var
    J: Integer;
  begin
    if (Pos('&', aItem.Caption) = 0) and
       (not aItem.IsLine) then // and
//       (not IsMenuBarItem(aMenu, aItem)) then //or (aItem.Count > 0) or (aItem.Tag <> 2)

      aItem.Caption := '&' + aItem.Caption; // Add & if missing

    for J := 0 to aItem.Count - 1 do
      FixItem(aItem.Items[J]);
  end;

begin
  for I := 0 to aMenu.Items.Count - 1 do
    for k := 0 to aMenu.items[I].Count -1 do
      FixItem(aMenu.Items[I]);
end;

I tried that approach, but it still adds the & to top-level menu bar items as well — so the issue remains. Even with separate loops or skipping directly in the main loop, the FixItem() call still affects them unless I explicitly check whether the item is a menu bar item.

Share this post


Link to post

Note that is the wrong inner loop statement. It needs to go a level deeper.

FixItem (aMenu.Items[I].items[k]);

 

 

 

 

 

 

Edited by Brian Evans
  • Like 1

Share this post


Link to post

Thanks, that worked correctly!

However, I'm still not knowing the native way to distinguish between true menu bar items (top-level items in the TMainMenu) and internal submenu items !

Share this post


Link to post
22 minutes ago, bravesofts said:

Thanks, that worked correctly!

However, I'm still not knowing the native way to distinguish between true menu bar items (top-level items in the TMainMenu) and internal submenu items !

You apparently missed my answer:

1 hour ago, dummzeuch said:

TMenuItem has got a Parent property of type TMenuItem. I haven't checked but I would assume that it is NIL for main menu items.

 

Edit: I was wrong. See online help here:

 

https://docwiki.embarcadero.com/Libraries/en/Vcl.Menus.TMenuItem.Parent

 

It's identical to the MainMenu's Items property. But that's just as good for detecting main menu items.

 

Share this post


Link to post

One approach: The Items property has a find() method that searches for a match of a caption passed in. It ignores & to make matching easier.

So if you have a reference to the main menu you can do mainmenuref.items.find()  passing in the caption of the menu item you wish to test.

   function isMenuMenuItem(MainMenu : TMainMenu; MenuItem : TMenuItem) : boolean;
   begin
     If MainMenu.Items.Find(MenuItem.Caption) <> nil then
       result := true
     else
       result := false;
   end;

 

Edited by Brian Evans

Share this post


Link to post
1 hour ago, dummzeuch said:

You apparently missed my answer:

Thanks, but I actually did try that earlier — checking if TMenuItem.Parent = nil. Unfortunately, in my specific case, it didn’t give the expected result. Even top-level menu items were affected, possibly due to the way OwnerDraw, when my MainMenu is hosted by TToolbar.

I also checked the documentation, and while Parent should be nil for main menu items, it didn’t behave consistently in my setup. That’s why I started exploring alternatives like checking the Windows hSubMenu via MENUITEMINFO.

Appreciate the suggestion though — it’s good in theory, just didn’t work reliably in my case.

Share this post


Link to post

Still - where and why? - in a lot of cases you can already know when your code is called.

 

For example OnDrawItem is set per item so you can just assign one procedure for main menu items and another for the rest.

Share this post


Link to post
1 hour ago, bravesofts said:

Thanks, but I actually did try that earlier — checking if TMenuItem.Parent = nil.

The Parent will not be nil for top-level items, it will instead be pointing at the TMainMenu.Items property.

Edited by Remy Lebeau

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×