WM_INITPOPUPMENU
消息,该消息“允许应用程序在显示菜单之前修改菜单,而不改变整个菜单。”问:为什么右键单击编辑控件时不会生成WM_INITMENUPOPUP消息?
答:我不能告诉你为什么没有,但我可以确认这是真的...编辑控件不会发送WM_INITMENUPOPUP。编辑控件必须使用空HWND句柄和/或TPM_NONOTIFY调用TrackPopupMenu,告诉菜单不要发送通知。可能(我只是猜测)作者试图通过减少消息流量来提高性能...无论如何,假设您想向编辑控件上下文菜单添加自己的菜单项。你该怎么办?唉,你别无选择,只能重新发明轮子。
因此,唯一可用的选项是对编辑控件进行子类化,并处理 WM_CONTEXTMENU
消息,根据需要创建并显示自定义弹出菜单。这意味着您必须手动复制您想要在自定义菜单中出现的任何标准菜单项的功能。
更新:事实上,有一种方法可以访问和修改编辑控件的标准弹出菜单(我刚刚测试过了,它有效)。TecMan提供了一个VBForums讨论的链接,但其中有些细节是错误的。我从PureBasic论坛讨论中得到了正确的细节。
正确的方法如下:
子类化编辑控件以拦截 WM_CONTEXTMENU
消息。可以使用 SetWindowSubClass()
或 SetWindowLongPtr(GWL_WNDPROC)
,但首选第一个。
接收到 WM_CONTEXTMENU
消息时,调用 SetWindowsHookEx()
安装线程本地钩子(将 0 用于 hMod
参数,将 GetCurrentThreadId()
用于 dwThreadId
参数)。可以使用 WH_CBT
或 WH_CALLWNDPROC
钩子。然后通过 DefSubclassProc()
或 CallWindowProc()
将 WM_CONTENTMENU
分派到默认消息处理程序,以调用标准弹出菜单。
在钩子过程中,当收到 HCBT_CREATEWND
(WH_CBT
钩子)或 WM_CREATE
(WH_CALLWNDPROC
钩子)通知时,将提供的 HWND
传递给 GetClassName()
。如果类名为 #32768
(作为MSDN文档中记录的菜单的标准窗口类名),则使用 PostMessage()
发送自定义窗口消息(非常重要!),在消息的 WPARAM
或 LPARAM
参数中指定菜单窗口的 HWND
,发送给任何您控制的 HWND
,例如您的主窗口,甚至是编辑控件本身(因为它已经被子类化)。您将需要下一步中的菜单的 HWND
。此时可以选择立即卸载钩子,或等待 DefSubclassProc()
/CallWindowProc()
退出(在菜单被关闭后退出)。因为此时菜单窗口尚未创建其 HMENU
,所以必须使用 PostMessage()
。 PostMessage()
将延迟到下一步,直到 HMENU
准备就绪。
收到自定义窗口消息时,通过 SendMessage()
向从钩子获取的菜单的 HWND
发送 MN_GETMENU
消息。现在您拥有了菜单的 HMENU
,可以随心所欲地使用它。
要禁用 Cut
、Copy
和 Paste
菜单项,请调用 EnableMenuItem()
。它们的菜单项标识符与相应的 WM_CUT
、WM_COPY
和 WM_PASTE
消息的值相同(这不是 Microsoft 记录的,但在 Windows 版本之间保持一致)。
更新:我刚刚找到了一个更简单的解决方案(在测试时也起作用)。
子类化编辑控件以拦截WM_CONTEXTMENU
消息,如上所述。
当接收到消息时,调用SetWinEventHook()
来安装一个线程本地事件钩子(将hmodWinEventProc
参数设置为0,idProcess
参数设置为GetCurrentProcessId()
,idThread
参数设置为GetCurrentThreadId()
,dwFlags
参数设置为0 - 而不是WINEVENT_INCONTEXT
!)。将eventMin
和eventMax
参数都设置为EVENT_SYSTEM_MENUPOPUPSTART
,这样它就是您接收到的唯一事件。然后将消息分派给默认处理程序以调用弹出菜单。
当调用您的事件回调函数时,菜单已经完全初始化,因此您可以向提供的HWND
发送MN_GETMENU
消息,它将是菜单的窗口(回调的idObject
参数将为OBJID_CLIENT
,idChild
参数将为0)。
根据需要操纵HMENU
。
在使用完成后取消钩子事件,如上所述。
As you can see here, this does work.
在这里可以看到,这确实有效。
Before modifying the menu:
在修改菜单之前:
禁用菜单项后:
即使删除菜单项:
GetMenuItemCount()
和GetMenuItemInfo()
来枚举菜单项,直到找到你感兴趣的项目,然后你将拥有它的ID(和索引),以便根据需要进行操作。 - Remy Lebeau您可以让选项保持可见,但锁定剪贴板以防止使用。
如果这个解决方案适合您,您只需要编写一个程序,通过调用OpenClipboard(NULL)
打开剪贴板。为了释放剪贴板,请调用CloseClipboard()
。
我在vbforums.com上发现了一个有趣的想法,可以获取编辑控件上下文菜单的句柄:
它演示了如何向标准操作系统上下文菜单添加自定义上下文菜单项。我认为,这个想法也可以用来修改菜单。理论上,我需要枚举菜单项并禁用与复制/粘贴命令相关的项目。问题是如何知道菜单项是否与复制/粘贴相关?获取菜单项文本是一个不好的主意 ;)
该代码的另一个问题是它基于一些未记录的Windows功能。我已经检查了解决方案,在Windows 10中仍然有效,但谁知道编辑控件上下文菜单在操作系统未来的更新中可能会发生什么变化...
HMENU
的技术,但其中一些细节是错误的。我已经更新了我的答案,提供了正确的细节。 - Remy Lebeau