如何在不关闭菜单的情况下选择菜单项?

Posted

技术标签:

【中文标题】如何在不关闭菜单的情况下选择菜单项?【英文标题】:How to select a Menu Item without closing the Menu? 【发布时间】:2011-08-24 09:40:15 【问题描述】:

默认情况下,当您从 TMainMenu 或 TPopupMenu 等中选择项目时,菜单会在单击后关闭。我想更改此行为,以便当我在菜单项上进行选择时,菜单不会关闭,但会在上次单击时保持可见并打开,这样如果需要,可以更轻松地选择另一个菜单项。当然,将焦点切换到另一个控件应该像平常一样隐藏菜单,但如果焦点仍在菜单上,请保持它可见。

如果可能,我希望此行为仅适用于指定的菜单项。换句话说,如果我可以让所有的菜单项正常工作,但如果我指定一两个菜单项,这些菜单项在选择时不会关闭。

我想这样做的原因是这样的,我的应用程序中有一个首选项表单,其中可以配置许多选项,通常的东西等,而且在主表单中我有一些常见的更常用的选项在 TMainMenu 中设置。我希望能够在不关闭菜单的情况下选择菜单中的这些常用选项,以便可以选择其他选项,例如无需浏览菜单项。

有没有标准化的方法来实现这一点?

谢谢

克雷格。

【问题讨论】:

+1。几分钟之内,许多“挑剔者”会争辩说这不是默认的 Windows 设计,这会使最终用户感到困惑。但是,为了您的辩护,您可以争辩说,Microsoft Office(至少 2010 年)应用程序中的 status bar context menu 仅举一个示例,其行为是这样的。这是一件非常好的事情,因为这个上下文菜单只包含复选框项,并且您可能会连续单击其中的几个。 好吧,我没有 Office 2010,但从链接中的屏幕截图来看,它似乎做了与我想要的类似的事情,唯一的区别是我使用的是标准 TMainMenu。 我认为使用标准菜单很难做到这一点。 @andreas 你经常是支持标准 UI 的主要吹毛求疵者之一。你改过自新了吗? ;-) 不知是否可以修改TActionMainMenuBar来实现这个(当然,只有当action manager的Style <> Platform default时)。 【参考方案1】:

基于@Sertac 的代码和其他资源,我制作了一个小单元,用于制作TPopupMenuTMainMenu 的Interposer 类(也适用于TNT 版本)。

它还处理子菜单(每次激活子菜单时,都会使用新的菜单句柄创建一个新的菜单窗口)。

这个想法是创建一个生命周期尽可能短的应用程序定义的钩子 (WH_CALLWNDPROC)。仅当菜单模式循环处于活动状态时,挂钩才会处于活动状态。一旦钩子检测到一个新的弹出窗口句柄(通过WM_ENTERIDLE),它就会继承它直到它被销毁。

.$DEFINE TNT
unit AppTrackMenus;

interface

uses
  Windows, Messages, SysUtils, Classes, Controls, Forms, Contnrs, Menus
  $IFDEF TNT, TntMenus$ENDIF;

type
  TTrackMenuNotifyEvent = procedure(Sender: TMenu; Item: TMenuItem; var CanClose: Boolean) of object;

  TPopupMenu = class(Menus.TPopupMenu)
  private
    FTrackMenu: Boolean;
    FOnTrackMenuNotify: TTrackMenuNotifyEvent;
  public
    procedure Popup(X, Y: Integer); override;
    property TrackMenu: Boolean read FTrackMenu write FTrackMenu;
    property OnTrackMenuNotify: TTrackMenuNotifyEvent read FOnTrackMenuNotify write FOnTrackMenuNotify;
  end;

  $IFDEF TNT
  TTntPopupMenu = class(TntMenus.TTntPopupMenu)
  private
    FTrackMenu: Boolean;
    FOnTrackMenuNotify: TTrackMenuNotifyEvent;
  public
    procedure Popup(X, Y: Integer); override;
    property TrackMenu: Boolean read FTrackMenu write FTrackMenu;
    property OnTrackMenuNotify: TTrackMenuNotifyEvent read FOnTrackMenuNotify write FOnTrackMenuNotify;
  end;
  $ENDIF

  TMainMenu = class(Menus.TMainMenu)
  private
    FTrackMenu: Boolean;
    FOnTrackMenuNotify: TTrackMenuNotifyEvent;
  public
    property TrackMenu: Boolean read FTrackMenu write FTrackMenu;
    property OnTrackMenuNotify: TTrackMenuNotifyEvent read FOnTrackMenuNotify write FOnTrackMenuNotify;
  end;

  $IFDEF TNT
  TTntMainMenu = class(TntMenus.TTntMainMenu)
  private
    FTrackMenu: Boolean;
    FOnTrackMenuNotify: TTrackMenuNotifyEvent;
  public
    property Hook: Boolean read FTrackMenu write FTrackMenu;
    property OnTrackMenuNotify: TTrackMenuNotifyEvent read FOnTrackMenuNotify write FOnTrackMenuNotify;
  end;
  $ENDIF

procedure FormMainMenuWndProcMessage(var Msg: TMessage; AForm: TCustomForm);

implementation    

const
   Undocumented Menu Messages 
  MN_SETHMENU                 = $01E0;
  MN_GETHMENU                 = $01E1;
  MN_SIZEWINDOW               = $01E2;
  MN_OPENHIERARCHY            = $01E3;
  MN_CLOSEHIERARCHY           = $01E4;
  MN_SELECTITEM               = $01E5;
  MN_CANCELMENUS              = $01E6;
  MN_SELECTFIRSTVALIDITEM     = $01E7;
  MN_GETPPOPUPMENU            = $01EA;
  MN_FINDMENUWINDOWFROMPOINT  = $01EB;
  MN_SHOWPOPUPWINDOW          = $01EC;
  MN_BUTTONDOWN               = $01ED;
  MN_MOUSEMOVE                = $01EE;
  MN_BUTTONUP                 = $01EF;
  MN_SETTIMERTOOPENHIERARCHY  = $01F0;
  MN_DBLCLK                   = $01F1;

var
  ActiveHookMenu: TMenu = nil;  

type
  TPopupWndList = class;

  TPopupWnd = class
  private
    FHandle: THandle;
    FMenuHandle: HMENU;
    FOrgPopupWindowProc, FHookedPopupWindowProc: Pointer;
    FSelectedItemPos: Integer;
    FSelectedItemID: UINT;
    FHooked: Boolean;
    FPopupWndList: TPopupWndList;
    function GetHMenu: HMENU;
    procedure PopupWindowProc(var Msg: TMessage);
    procedure Hook;
    procedure UnHook;
    procedure MenuSelectPos(Menu: TMenu; ItemPos: UINT; out CanClose: Boolean);
    procedure MenuSelectID(Menu: TMenu; ItemID: UINT; out CanClose: Boolean);
  public
    property Handle: THandle read FHandle write FHandle;
    property MenuHandle: HMENU read FMenuHandle;
    constructor Create(APopupWndList: TPopupWndList; AHandle: THandle); overload;
    destructor Destroy; override;
  end;

  TPopupWndList = class(TObjectList)
  public
    function FindHookedPopupHWnd(MenuWindow: HWND): TPopupWnd;
    function FindHookedPopupHMenu(Menu: HMENU): TPopupWnd;
  end;

 TPopupWnd 
constructor TPopupWnd.Create(APopupWndList: TPopupWndList; AHandle: THandle);
begin
  inherited Create;
  FHandle := AHandle;
  FMenuHandle := GetHMenu;
  FPopupWndList := APopupWndList;
  Hook;
end;

destructor TPopupWnd.Destroy;
begin
  if FHooked then // JIC: normally UnHook is called in PopupWindowProc WM_DESTROY
    UnHook;
  inherited;
end;

procedure TPopupWnd.Hook;
begin
  FOrgPopupWindowProc := Pointer(GetWindowLong(FHandle, GWL_WNDPROC));
  FHookedPopupWindowProc := MakeObjectInstance(PopupWindowProc);
  SetWindowLong(FHandle, GWL_WNDPROC, Longint(FHookedPopupWindowProc));
  FHooked := True;
end;

procedure TPopupWnd.UnHook;
begin
  SetWindowLong(FHandle, GWL_WNDPROC, Longint(FOrgPopupWindowProc));
  FreeObjectInstance(FHookedPopupWindowProc);
  FHooked := False;
end;

procedure TPopupWnd.PopupWindowProc(var Msg: TMessage);
var
  NormalItem: Boolean;
begin
  case Msg.Msg of
    MN_SELECTITEM:
      begin
        // -1 ($FFFF) => mouse is outside the menu window  
        FSelectedItemPos := Integer(Msg.wParam); // HiWord(Msg.wParam)
      end;
    MN_DBLCLK:
      begin
        Exit; // eat
      end;
    MN_BUTTONDOWN:
      begin
        MenuSelectPos(ActiveHookMenu, UINT(Msg.WParamLo), NormalItem);
        if not NormalItem then
          Exit;
      end;
    WM_KEYDOWN:
      if (Msg.WParam = VK_RETURN) and (FSelectedItemPos <> -1) and (FSelectedItemID <> 0) then begin            
        MenuSelectID(ActiveHookMenu, FSelectedItemID, NormalItem);
        if not NormalItem then
          Exit;
      end;
    WM_DESTROY:
      begin            
        UnHook;
      end;
  end;
  Msg.Result := CallWindowProc(FOrgPopupWindowProc, FHandle, Msg.Msg, Msg.WParam, Msg.LParam);
end;

procedure TPopupWnd.MenuSelectPos(Menu: TMenu; ItemPos: UINT; out CanClose: Boolean);
begin
  MenuSelectID(Menu, GetMenuItemID(GetHMenu, ItemPos), CanClose);
end;

function GetMenuItemPos(Menu: HMENU; ItemID: UINT): Integer;
var
  I: Integer;
  MenuItemInfo: TMenuItemInfo;
begin
  Result := -1;                         
  if IsMenu(Menu) then
    for I := 0 to GetMenuItemCount(Menu) do
    begin
      FillChar(MenuItemInfo, SizeOf(MenuItemInfo), 0);
      MenuItemInfo.cbSize := SizeOf(MenuItemInfo);
      MenuItemInfo.fMask := MIIM_ID;
      if (GetMenuItemInfo(Menu, I, True, MenuItemInfo)) then
        if MenuItemInfo.wID = ItemID then
        begin
          Result := I;
          Exit;
        end;
    end;
end;

procedure TPopupWnd.MenuSelectID(Menu: TMenu; ItemID: UINT; out CanClose: Boolean);
var
  Item: TMenuItem;
  NotifyEvent: TTrackMenuNotifyEvent;
  R: TRect;
begin
  CanClose := True;
  Item := Menu.FindItem(ItemID, fkCommand);
  if Assigned(Item) then
  begin
    NotifyEvent := nil;
    $IFDEF TNT
    if Menu is TTntPopupMenu then
      NotifyEvent := TTntPopupMenu(Menu).FOnTrackMenuNotify
    else
    $ENDIF
    if Menu is TPopupMenu then
      NotifyEvent := TPopupMenu(Menu).FOnTrackMenuNotify
    else
    $IFDEF TNT
    if Menu is TTntMainMenu then
      NotifyEvent := TTntMainMenu(Menu).FOnTrackMenuNotify
    else
    $ENDIF
    if Menu is TMainMenu then
      NotifyEvent := TMainMenu(Menu).FOnTrackMenuNotify;

    if Assigned(NotifyEvent) then
      NotifyEvent(Menu, Item, CanClose);

    if not CanClose then
    begin
      Item.Click;
      if GetMenuItemRect(FHandle, FMenuHandle, GetMenuItemPos(FMenuHandle, ItemID), R) then
      begin
        MapWindowPoints(0, FHandle, R, 2);
        InvalidateRect(FHandle, @R, False);
      end else
        InvalidateRect(FHandle, nil, False);
    end;
  end;
end;

function TPopupWnd.GetHMenu: HMENU;
begin
  Result := SendMessage(FHandle, MN_GETHMENU, 0, 0);
end;

 TPopupWndList 
function TPopupWndList.FindHookedPopupHWnd(MenuWindow: HWND): TPopupWnd;
var
  I: Integer;
  PopupWnd: TPopupWnd;
begin
  Result := nil;
  for I := 0 to Count - 1 do
  begin
    PopupWnd := TPopupWnd(Items[I]);
    if (PopupWnd.FHooked) and (PopupWnd.Handle = MenuWindow) then
    begin
      Result := PopupWnd;
      Exit;
    end;
  end;
end;

function TPopupWndList.FindHookedPopupHMenu(Menu: HMENU): TPopupWnd;
var
  I: Integer;
  PopupWnd: TPopupWnd;
begin
  Result := nil;
  for I := 0 to Count - 1 do
  begin
    PopupWnd := TPopupWnd(Items[I]);
    if (PopupWnd.FHooked) and (PopupWnd.MenuHandleGetHMenu = Menu) then
    begin
      Result := PopupWnd;
      Exit;
    end;
  end;
end;

var
  PopupWndList: TPopupWndList = nil;
  MenuCallWndHook: HHOOK = 0;
  SelectedItemID: UINT = 0;
  NeedPopupWindowHandle: Boolean = False;
  InitMenuPopupCount: Integer = 0;

function CallWndHookProc(nCode: Integer; wParam: WPARAM; Msg: PCWPStruct): LRESULT; stdcall;
var
  Menu: HMENU;
  MenuWnd: HWND;
  PopupWnd: TPopupWnd;
begin
  if (nCode = HC_ACTION) then
  begin
    case Msg.message of
      WM_INITMENUPOPUP:
        begin // TWMInitMenuPopup
          Inc(InitMenuPopupCount);
          NeedPopupWindowHandle := True;
          SelectedItemID := 0;
          if PopupWndList = nil then
          begin
            PopupWndList := TPopupWndList.Create(True); // OwnsObjects
          end;
        end;
      WM_UNINITMENUPOPUP:
        begin
          Dec(InitMenuPopupCount);
        end;
      WM_ENTERIDLE:
        begin
          if (Msg.wParam = MSGF_MENU) and NeedPopupWindowHandle then
          begin
            NeedPopupWindowHandle := False;
            MenuWnd := HWND(Msg.lParam);
            if Assigned(PopupWndList) and (PopupWndList.FindHookedPopupHWnd(MenuWnd) = nil) then
              PopupWndList.Add(TPopupWnd.Create(PopupWndList, MenuWnd));
          end;
        end;
      WM_MENUSELECT:
        begin
          // MSDN: If the high-order word of wParam contains 0xFFFF and the lParam parameter contains NULL, the system has closed the menu.
          if (Msg.lParam = 0) and (HiWord(Msg.wParam) = $FFFF) then // Menu Closed
          begin
            FreeAndNil(PopupWndList);
          end
          else
          begin
            Menu := HMENU(Msg.lParam);
            if HiWord(Msg.wParam) and MF_POPUP <> 0 then // fkHandle
              SelectedItemID := GetSubMenu(Menu, LoWord(Msg.WParam))
            else // fkCommand
              SelectedItemID := LoWord(Msg.wParam); // TWMMenuSelect(Msg).IDItem;
            if Assigned(PopupWndList) then
            begin
              PopupWnd := PopupWndList.FindHookedPopupHMenu(Menu);
              if Assigned(PopupWnd) then
              begin
                PopupWnd.FSelectedItemID := LoWord(Msg.wParam);
              end;
            end;
          end;
        end;
    end;
  end;
  Result := CallNextHookEx(MenuCallWndHook, nCode, WParam, Longint(Msg));
end;

procedure InstallMenuCallWndHook(Menu: TMenu);
begin
  ActiveHookMenu := Menu;
  MenuCallWndHook := SetWindowsHookEx(WH_CALLWNDPROC, @CallWndHookProc, 0, GetCurrentThreadId);
end;

procedure UnInstallMenuCallWndHook;
begin
  if MenuCallWndHook <> 0 then
    UnHookWindowsHookEx(MenuCallWndHook);
  MenuCallWndHook := 0;
  ActiveHookMenu := nil;
  PopupWndList := nil;
end;

 TPopupMenu 
procedure TPopupMenu.Popup(X, Y: Integer);
begin
  if not FTrackMenu then
    inherited
  else
  try
    InstallMenuCallWndHook(Self);
    inherited;
  finally
    UnInstallMenuCallWndHook;
  end;
end;

 TTntPopupMenu 
$IFDEF TNT
procedure TTntPopupMenu.Popup(X, Y: Integer);
begin
  if not FTrackMenu then
    inherited
  else
  try
    InstallMenuCallWndHook(Self);
    inherited;
  finally
    UnInstallMenuCallWndHook;
  end;
end;
$ENDIF

function GetMenuForm(Menu: TMenu): TCustomForm;
var
  LForm: TWinControl;
begin
  Result := nil;
  if Menu.WindowHandle <> 0 then
  begin
    LForm := FindControl(Menu.WindowHandle);
    if (LForm <> nil) and (LForm is TCustomForm) then
      Result := LForm as TCustomForm;
  end;
end;

function FormMainMenuIsValid(AForm: TCustomForm): Boolean;
begin
  Result := False;
  if Assigned(AForm) and Assigned(AForm.Menu) then
  begin
    $IFDEF TNT
    if (AForm.Menu is TTntMainMenu) then
      Result := TTntMainMenu(AForm.Menu).FTrackMenu
    else
    $ENDIF
    if (AForm.Menu is TMainMenu) then
      Result := TMainMenu(AForm.Menu).FTrackMenu;
  end;
end;

procedure FormMainMenuWndProcMessage(var Msg: TMessage; AForm: TCustomForm);
begin
  if not FormMainMenuIsValid(AForm) then
    Exit;

  case Msg.Msg of
    WM_INITMENU:
      begin
        // MSDN: Sent when a menu is about to become active. It occurs when the user clicks an item on the menu bar or presses a menu key.
        // A window receives this message through its WindowProc function
        // A WM_INITMENU message is sent only when a menu is first accessed; only one WM_INITMENU message is generated for each access.
        // For example, moving the mouse across several menu items while holding down the button does not generate new messages
        InstallMenuCallWndHook(AForm.Menu);
      end;
    WM_MENUSELECT:
      begin
        // MSDN: If the high-order word of wParam contains 0xFFFF and the lParam parameter contains NULL, the system has closed the menu.
        if (Msg.lParam = 0) and (HiWord(Msg.wParam) = $FFFF) then // Menu Closed
        begin
          UnInstallMenuCallWndHook;
        end;
      end;
  end;
end;

end.

用法:

在表单上添加TPopupMenu 和/或TMainMenu。在uses 中包含AppTrackMenus 之后 Menus。创建一些菜单项,并为您希望在单击时不关闭的每个菜单项设置Tag=666(对于本示例)。您可以为每个项目分配一个OnClick 事件处理程序CheckNoCloseClick

uses 
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  Menus, AppTrackMenus;

TForm1 = class(TForm)
...
  procedure CheckNoCloseClick(Sender: TObject);
protected
  procedure WndProc(var Msg: TMessage); override; // for TMainMenu
private
  procedure TrackMenuNotifyHandler(Sender: TMenu; Item: TMenuItem; var CanClose: Boolean);
end;

implementation

procedure TForm1.FormCreate(Sender: TObject);
begin
  PopupMenu1.TrackMenu := True;
  PopupMenu1.OnTrackMenuNotify := TrackMenuNotifyHandler;
  MainMenu1.TrackMenu := True;
  MainMenu1.OnTrackMenuNotify := TrackMenuNotifyHandler;
end;

procedure TForm1.CheckNoCloseClick(Sender: TObject);
begin
  TMenuItem(Sender).Checked := not TMenuItem(Sender).Checked;
end;

procedure TForm1.TrackMenuNotifyHandler(Sender: TMenu; Item: TMenuItem; var CanClose: Boolean);
begin
  Caption := Sender.ClassName + '-' + Item.ClassName + '-' + Item.Name;
  CanClose := Item.Tag <> 666;
end;

procedure TForm1.WndProc(var Msg: TMessage); // for TMainMenu
begin
  FormMainMenuWndProcMessage(Msg, Self);
  inherited;
end;

TMainMenu Interposer 可以通过在运行时根据需要(通过设置新的Form.WindowProc)对其表单进行子类化来改进,而无需为每个表单覆盖WndProc。但是,每个应用程序通常只有一个主菜单。也许下一个版本... :)

在 XP/Vista/Win7 中测试

【讨论】:

谢谢,伙计,你节省了我很多时间。对你的业力有很多好处! @kobik 不适用于 64 位可执行文件。作品:XP32bit和Win1064bit上32个可执行文件。失败:64个可执行文件。我还不知道为什么,但是您可以通过单击菜单外部来暴露问题(当菜单即将关闭/关闭时似乎会发生) 更准确地说,你是通过弹出菜单(我从工具栏做的)-然后单击菜单外的任何地方(通常会关闭菜单)-我收到错误 $C0000005 访问冲突。但似乎发生在 Windows 内部 - Delphi 调用堆栈不会显示确切位置,除非它发生在 Popup InstallMenuCallWndHook 中 @Tom,对不起,我没有要测试的 64 位编译器。我怀疑GetWindowLong 应该替换为GetWindowLongPtr 等... @kobik 修复这些似乎还不够 - 但是是的 - 这可能是一个 32/64 位问题。当/如果我得到更多信息时,我会回来。【参考方案2】:

在下面的代码中,当右键单击表单上的面板时,会启动一个包含三个项目的弹出菜单。第一项行为正常,其他两项也触发其点击事件,但弹出菜单未关闭。

弹出窗口是使用“TrackPopupMenu”启动的,如果您想使用“OnPopup”事件,或者需要使用具有非关闭项目的子菜单,请参阅我发布到您的问题的评论中的链接。调整主菜单的代码也不会很困难..

我没有评论代码不是为了推广该方法的使用,因为它使用了未记录的消息,而且我觉得它有点令人费解..

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, Menus, ExtCtrls;

type
  TForm1 = class(TForm)
    PopupMenu1: TPopupMenu;
    Item1Normal1: TMenuItem;
    Item2NoClose1: TMenuItem;
    Item3NoClose1: TMenuItem;
    Panel1: TPanel;
    procedure Panel1ContextPopup(Sender: TObject; MousePos: TPoint;
      var Handled: Boolean);
  private
    FGetPopupWindowHandle: Boolean;
    FPopupWindowHandle: HWND;
    OrgPopupWindowProc, HookedPopupWindowProc: Pointer;
    FSelectedItemID: UINT;
    procedure WmInitMenuPopup(var Msg: TWMInitMenuPopup); message WM_INITMENUPOPUP;
    procedure WmEnterIdle(var Msg: TWMEnterIdle); message WM_ENTERIDLE;
    procedure WmMenuSelect(var Msg: TWMMenuSelect); message WM_MENUSELECT;
    procedure PopupWindowProc(var Msg: TMessage);
    procedure MenuSelectPos(Menu: TMenu; ItemPos: UINT; out CanClose: Boolean);
    procedure MenuSelectID(Menu: TMenu; ItemID: UINT; out CanClose: Boolean);
  public
  end;

var
  Form1: TForm1;

implementation

$R *.dfm

procedure TForm1.Panel1ContextPopup(Sender: TObject; MousePos: TPoint;
  var Handled: Boolean);
var
  Pt: TPoint;
begin
  Pt := (Sender as TPanel).ClientToScreen(MousePos);
  TrackPopupMenu(PopupMenu1.Handle, 0, Pt.X, Pt.Y, 0, Handle, nil);
end;

procedure TForm1.WmInitMenuPopup(var Msg: TWMInitMenuPopup);
begin
  inherited;
  if Msg.MenuPopup = PopupMenu1.Handle then
    FGetPopupWindowHandle := True;
end;

procedure TForm1.WmEnterIdle(var Msg: TWMEnterIdle);
begin
  inherited;
  if FGetPopupWindowHandle then begin
    FGetPopupWindowHandle := False;
    FPopupWindowHandle := Msg.IdleWnd;

    HookedPopupWindowProc := classes.MakeObjectInstance(PopupWindowProc);
    OrgPopupWindowProc := Pointer(GetWindowLong(FPopupWindowHandle, GWL_WNDPROC));
    SetWindowLong(FPopupWindowHandle, GWL_WNDPROC, Longint(HookedPopupWindowProc));
  end;
end;

procedure TForm1.WmMenuSelect(var Msg: TWMMenuSelect);
begin
  inherited;
  if Msg.Menu = PopupMenu1.Handle then
    FSelectedItemID := Msg.IDItem;
end;


const
  MN_BUTTONDOWN = $01ED;

procedure TForm1.PopupWindowProc(var Msg: TMessage);
var
  NormalItem: Boolean;
begin
  case Msg.Msg of
    MN_BUTTONDOWN:
      begin
        MenuSelectPos(PopupMenu1, UINT(Msg.WParamLo), NormalItem);
        if not NormalItem then
          Exit;
      end;
    WM_KEYDOWN:
      if Msg.WParam = VK_RETURN then begin
        MenuSelectID(PopupMenu1, FSelectedItemID, NormalItem);
        if not NormalItem then
          Exit;
      end;
    WM_DESTROY:
      begin
        SetWindowLong(FPopupWindowHandle, GWL_WNDPROC, Longint(OrgPopupWindowProc));
        classes.FreeObjectInstance(HookedPopupWindowProc);
      end;
  end;

  Msg.Result := CallWindowProc(OrgPopupWindowProc, FPopupWindowHandle,
      Msg.Msg, Msg.WParam, Msg.LParam);

end;


procedure TForm1.MenuSelectID(Menu: TMenu; ItemID: UINT; out CanClose: Boolean);
var
  Item: TMenuItem;
begin
  CanClose := True;
  Item := Menu.FindItem(ItemID, fkCommand);
  if Assigned(Item) then begin
    // Menu Item is clicked
    Item.Click;
//    Panel1.Caption := Item.Name;
    CanClose := Item = Item1Normal1;
  end;
end;

procedure TForm1.MenuSelectPos(Menu: TMenu; ItemPos: UINT; out CanClose: Boolean);
begin
  MenuSelectID(Menu, GetMenuItemID(Menu.Handle, ItemPos), CanClose);
end;

end.

【讨论】:

出色的工作,我已经测试过,这与弹出菜单完美配合。我会查看您发布的链接,看看我是否可以适应主菜单。您的回答已被接受,谢谢。 @Craig,谢谢,不客气!我在评论中提到的答案与自动启动的弹出窗口(控件的“PopupMenu”属性)相当相关,并且还提到了这个答案中忽略的一些东西,比如子菜单、加速键(还有更多工作要做。 .)。可能您还必须考虑其他一些特殊情况,f.i.禁用项目..我相信主菜单处理将类似于此答案,因为菜单消息被发送到表单.. 不错的答案和 +1。我posted a question 变成了重复。但是您的代码有一个小问题:如果我在Item.Click; (MenuSelectID) 之后设置Item.Checked := not Item.Checked - 项目不会失效,并且在我离开项目区域并回车之前不会绘制支票:(可以做什么? 在修复它之后添加InvalidateRect(FPopupWindowHandle, nil, False);... :) @ZigZ - 很高兴你解决了它,我不太记得我在这里做了什么:) .. 感谢您的投票。 :)【参考方案3】:

我最近有同样的需求,发现 TMS Smooth 控件有“撕掉”菜单,它具有类似的功能,但要求(如名称所示)菜单被,嗯,撕掉!我从来没有考虑过,因为我的需求不足以证明时间、金钱或使用第三方产品的合理性。但是,我用过他们其他一流的东西。

不确定他们的撕下菜单是否能满足您的需求,但您可能想研究一下。

http://www.tmssoftware.com/site/advsmoothmegamenu.asp

【讨论】:

【参考方案4】:

我的猜测是,虽然这是可以接受的,但您可能应该考虑编写自己的菜单系统,使用面板或表单,或完整的自定义控件/组件集,如果您愿意,则根本不使用标准的 TPopupMenu 或 TMainMenu这样做。

如果你想要一些入门源代码,我会从 Toolbar2000+SpTBX Sources 之类的东西开始。我很确定您可以使用它们来完成此操作,但不能使用 TMainMenu 和 TPopupMenu,因为它们包装了一些 Win32 内置程序,这些内置程序具有无法覆盖的行为(包括在您不想时关闭)。

您还可以使用 Developer Express 工具栏组件开箱即用地执行此类操作。

【讨论】:

那里有一些非常好的建议。然而,面板/表单的想法可能是不可行的,特别是因为这意味着需要大量维护它(项目、图像等)。我没有 DevExpress 组件套件,我知道您必须为此付费。 如果有人知道其他人完成了使用菜单中的信息初始化自定义表单的任务(例如,用于控制 50+ 列的可见性)......弹出菜单包含足够的信息,它应该是可以创建一个通用的自定义 popupmenu 表单替换重新创建菜单......但仍有相当多的工作

以上是关于如何在不关闭菜单的情况下选择菜单项?的主要内容,如果未能解决你的问题,请参考以下文章

在 WinForms 中为上下文菜单动态选择菜单项的正确方法是啥?

选择菜单项时无法显示组

如何在不选择下拉菜单的情况下折叠/关闭组合框?

在 react material-ui 菜单中测试嵌套菜单项

在菜单选择上渲染部分视图

JSF Ajax Update 不更新组件