Typically, when a control displays a popup menu, a WM_INITPOPUPMENU
message is generated which "allows an application to modify the menu before it is displayed, without changing the entire menu."
Unfortunately, a standard Win32 Edit control does not generate that message for its default popup menu, as confirmed in a November 2000 article of MSDN Magazine (the link on MSDN itself is dead, but this link is from the Internet Archive):
MSDN Magazine, November 2000, C++ Q&A:
Q: Why isn't a WM_INITMENUPOPUP message generated when you right-click an edit control?
A: I can't tell you why there isn't one, but I can confirm it's true ... edit controls don't send WM_INITMENUPOPUP. The edit control must be calling TrackPopupMenu with a null HWND handle and/or TPM_NONOTIFY, which tells the menu not to send notifications. It's possible (and again I'm only guessing) that the authors were trying to improve performance by reducing message traffic ... In any case, suppose you want to add your own menu items to the edit control context menu. How do you do it? Alas, you have no choice but to reinvent the wheel
So the only option available is to subclass the edit control and handle the WM_CONTEXTMENU
message instead, creating and displaying your own custom popup menu as needed. Which means you have to manually duplicate the functionality of any standard menu items that you want to appear in your custom menu.
Update: there is a way to access and modify the edit control's standard popup menu after all (I just tested it and it worked). TecMan provided a link to a VBForums discussion that talks about it, however it gets a few details wrong. I got the correct details from a PureBasic forum discussion.
The correct approach is as follows:
subclass the edit control to intercept the WM_CONTEXTMENU
message. Either SetWindowSubClass()
or SetWindowLongPtr(GWL_WNDPROC)
can be used, though the first is preferred.
when the WM_CONTEXTMENU
message is received, call SetWindowsHookEx()
to install a thread-local hook (use 0 for the hMod
parameter and GetCurrentThreadId()
for the dwThreadId
parameter). Either a WH_CBT
or WH_CALLWNDPROC
hook can be used. Then dispatch WM_CONTENTMENU
to the default message handler via DefSubclassProc()
or CallWindowProc()
to invoke the standard popup menu.
inside the hook procedure, when a HCBT_CREATEWND
(WH_CBT
hook) or WM_CREATE
(WH_CALLWNDPROC
hook) notification is received, pass the provided HWND
to GetClassName()
. If the class name is #32768
(the standard window class name for menus, as documented on MSDN), post (very important!) a custom window message using PostMessage()
, specifying the menu window's HWND
in the message's WPARAM
or LPARAM
parameter, to any HWND
that you control, such as your main window, or even the edit control itself (since it is already subclassed). You will need the menu's HWND
in the next step. You can optionally now uninstall the hook at this time, or wait for DefSubclassProc()
/CallWindowProc()
to exit (it will exit after the menu has been dismissed). You need to use PostMessage()
because the menu window has not created its HMENU
yet at this time. PostMessage()
delays the next step until after the HMENU
is ready.
when the custom window message is received, send a MN_GETMENU
message via SendMessage()
to the menu's HWND
that you obtained from the hook. You now have the menu's HMENU
and can do whatever you want with it.
to disable the Cut
, Copy
, and Paste
menu items, call EnableMenuItem()
. Their menu item identifiers are the same values as the WM_CUT
, WM_COPY
and WM_PASTE
messages, respectively (this is not documented by Microsoft, but is consistent across Windows versions).
Update: I just found a much simpler solution (which also worked when I tested it).
subclass the edit control to intercept WM_CONTEXTMENU
, as described above.
when the message is received, call SetWinEventHook()
to install a thread-local event hook (set the hmodWinEventProc
parameter to 0, the idProcess
parameter to GetCurrentProcessId()
, the idThread
parameter to GetCurrentThreadId()
, and the dwFlags
parameter to 0 - not WINEVENT_INCONTEXT
!). Set the eventMin
and eventMax
parameters both to EVENT_SYSTEM_MENUPOPUPSTART
so that it is the only event you receive. Then dispatch the message to the default handler to invoke the popup menu.
when your event callback is called, the menu has already been fully initialized, so you can send the MN_GETMENU
message to the provided HWND
, which will be the menu's window (the callback's idObject
parameter will be OBJID_CLIENT
and the idChild
parameter will be 0).
manipulate the HMENU
as needed.
unhook the event hook when done using it, as described above.
As you can see here, this does work.
Before modifying the menu:
After disabling the menu items:
Even deleting the menu items:
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…