bravesofts 29 Posted 8 hours ago (edited) 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 7 hours ago by bravesofts Share this post Link to post
Minox 8 Posted 8 hours ago You could use the "Tag" property to distinguish them. Share this post Link to post
dummzeuch 1629 Posted 8 hours ago (edited) 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 7 hours ago by dummzeuch Share this post Link to post
bravesofts 29 Posted 8 hours ago 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
Brian Evans 122 Posted 7 hours ago (edited) 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 7 hours ago by Brian Evans Share this post Link to post
bravesofts 29 Posted 7 hours ago 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
Brian Evans 122 Posted 7 hours ago (edited) Note that is the wrong inner loop statement. It needs to go a level deeper. FixItem (aMenu.Items[I].items[k]); Edited 7 hours ago by Brian Evans 1 Share this post Link to post
bravesofts 29 Posted 7 hours ago 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
dummzeuch 1629 Posted 6 hours ago 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
Brian Evans 122 Posted 6 hours ago (edited) 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 6 hours ago by Brian Evans Share this post Link to post
bravesofts 29 Posted 5 hours ago 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
Brian Evans 122 Posted 4 hours ago 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
Remy Lebeau 1574 Posted 3 hours ago (edited) 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 3 hours ago by Remy Lebeau Share this post Link to post