Search code examples
delphiwinapidelphi-7delphi-2010

How to make Tpopupmenu to pre-select the 1st menu entry when a popup is opened with the keyboard


The items in TPopupMenu can be highlighted/selected with keyboard or mouse. When selected with keyboard, you can move in the menu with the arrow keys.

How to mark the 1st menu item as selected (blue) without simulating the down arrow keypress with VK_DOWN (see code below)?

Popup := TPopupMenu.create(nil);
Popup.OnPopup := PopupClick;
class procedure TTrayMain.PopupClick(Sender: TObject) ;
begin    
  // Code below activates the first menu entry.. 
  // Now Looking for an alternative solution to replace this hack:
  keybd_event( VK_DOWN, MapVirtualKey( VK_DOWN,0), 0, 0);
  keybd_event( VK_DOWN, MapVirtualKey( VK_DOWN,0), KEYEVENTF_KEYUP, 0);

end;

Solution

  • You need to send the undocumented MN_SELECITEM message to the popupwindow.

    unit Unit1;
    
    interface
    
    uses
      Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
      Dialogs, Menus, ExtCtrls, StdCtrls;
    
    type
      TForm1 = class(TForm)
        PopupMenu1: TPopupMenu;
        Item1: TMenuItem;
        Item2: TMenuItem;
        Item3: TMenuItem;
      private
        { Private declarations }
      public
        { Public declarations }
      end;
    
      //To override the default PopupList, based on Remy Lebeau's code
      TPopupListEx = class(TPopupList)
      protected
        procedure WndProc(var Message: TMessage); override;
      end;
    
    var
      Form1: TForm1;
    
    implementation
    
    {$R *.dfm}
    
    const MN_SELECTITEM = $01E5;
    
    { TPopupListEx }
    
    procedure TPopupListEx.WndProc(var Message: TMessage);
    var hm:HMENU;
    
    begin
      inherited;
      if (Message.Msg = WM_ENTERMENULOOP) and (Message.WParam = 1) then
        begin
          //When the popupwindow is already created, we can ask it's handle
          hm:=FindWindow(PChar('#32768'),nil);
          //Send the MN_SELECTITEM message. The third parameter is the desired menuitem's index.
          SendMessage(hm,MN_SELECTITEM,0,0);
        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.
    

    Note about other (including the mentioned delphipraxis sample) solutions:

    Setting the MenuItem to a highlight state with SetMenuItemInfoW or with HiliteMenuItem won't work, since it's only affect the appearance, but when you hover the mouse over any other item, the first item remains highlighted.