Search code examples
delphimenuitemdelphi-10.3-rio

How to HIGHLIGHT a specific popup menu item?


I have this popup menu:

object pmTest: TPopupMenu
  OnPopup = pmTestPopup
  Left = 32
  Top = 24
  object mTest1: TMenuItem
    Caption = 'Test 1'
    OnClick = mTest1Click
  end
  object mTest2: TMenuItem
    Caption = 'Test 2'
    OnClick = mTest2Click
  end
end

After showing the popup menu with the Popup method, I need to programmatically HIGHLIGHT (not to click!) a specific menu item, so I tried this:

Winapi.Windows.HiliteMenuItem(pmTest.WindowHandle, pmTest.Handle, 1, MF_BYPOSITION or MF_HILITE);

But it does not work.

How can I programmatically HIGHLIGHT a specific popup menu item?


Solution

  • By default, the TPopupMenu.WindowHandle property is set to the Application.Handle window, not to the private HWND that TPopupMenu actually uses internally to actually dispatch its WM_COMMAND messages to. That window is established when the TPopupMenu.Popup() method is called, and it does not update the TPopupMenu.WindowHandle property.

    Try using the TPopupList.Window property instead of the TPopupMenu.WindowHandle property for the HWND to pass to HiliteMenuItem(). There is a global PopupList object in the Vcl.Menus unit:

    procedure TMyForm.pmTestPopup(Sender: TObject);
    begin
      Winapi.Windows.HiliteMenuItem({pmTest.WindowHandle}PopupList.Window, pmTest.Handle, 1, MF_BYPOSITION or MF_HILITE);
    end;
    

    If that still does not work, then try the Win32 SetMenuItemInfo() function instead, which does not take an HWND for input:

    procedure TMyForm.pmTestPopup(Sender: TObject);
    var
      mii: MENUITEMINFO;
    begin
      ZeroMemory(@mii, sizeof(mii));
      mii.cbSize := sizeof(mii);
      mii.fMask := MIIM_STATE;
      mii.fState := MFS_HILITE;
    
      Winapi.Windows.SetMenuItemInfoW(pmTest.Handle, 1, True, mii);
    end;
    

    UPDATE: Upon further review, the TPopupMenu.OnPopup event is fired BEFORE the menu is made visible, and TPopupMenu may recreate the menu AFTER OnPopup has been called and BEFORE the menu is actually shown. So, your best bet is likely to subclass the TPopupList window so you can intercept the WM_ENTERMENULOOP message, then customize your menu items at that point. For example:

    type
      TPopupListEx = class(TPopupList)
      protected
        procedure WndProc(var Message: TMessage); override;
      end;
    
    procedure TPopupListEx.WndProc(var Message: TMessage);
    begin
      inherited;
      if (Message.Msg = WM_ENTERMENULOOP) and (Message.WParam = 1) then
      begin
        // customize pmTest items as needed...
      end;
    end;
    
    initialization
      Popuplist.Free; //free the "default", "old" list
      PopupList := TPopupListEx.Create; //create the new one
      // The new PopupList will be freed by
      // finalization section of Menus unit.
    end.