Context menus (or "right-click popup" menus) come in two flavors: application-wide or control-specific. This article focuses on the former. If you are interested in control-specific menus, be sure to check out my article on that very subject.
Implementing the Application Popup Menu
The first step in creating a context menu is simple: use the Visual Studio resource editor to create the menu you wish to display. Make sure that you give it a unique ID; I will use the value IDR_POPUP_MENU
for this example. Add to the menu the various menu items that you want to include. Note that your context menu should only have one top-level item (under which all other menu items will be placed). The caption for this top-level item can be whatever you like since the user will never see it. If you are mapping menu items in this context menu to commands that already exist in the program (as is likely), make sure that you give each menu item the appropriate ID. For example, if the ID to my "Open File" command in my main application menu is ID_FILE_OPEN
, I would give that same ID to the corresponding item in my context menu. If you aren't mapping commands, simply add any message handlers as normal.
In order to display the context menu to the user when they click the right mouse button, we need to add a handler for the WM_CONTEXTMENU
message. This handler should be added to the window class that will handle the menu's message calls. Most often, this class will be your view class, but note that any CWnd
based class will do.
Once the message handler has been added, insert the following code into the WM_CONTEXTMENU
handler's method:
// We might need to adjust the origination point for the
// keyboard context menu
if(point.x == -1 && point.y == -1)
{
CRect rect;
GetClientRect(&rect);
point = rect.TopLeft();
point.Offset(5,5);
ClientToScreen(&point);
}
// Load the top level menu from the resource we created
CMenu myMenu;
myMenu.LoadMenu(IDR_POPUP_MENU);
// Now extract the (one and only) popup menu item from
// this menu resource
CMenu* myPopup = myMenu.GetSubMenu(0);
// Let's display the menu!
myPopup->TrackPopupMenu(TPM_LEFTALIGN | TPM_LEFTBUTTON,
point.x, point.y, AfxGetMainWnd(), NULL);
Understanding the Code
The first thing we do is to handle the case where the user invokes our context menu via the keyboard. In this scenario, we adjust the origination point for the menu. Next, we load the menu resource that we created earlier and extract the popup menu item (the one and only top-level item). The popup menu extraction is performed by using the GetSubMenu()
method, and passing the (zero-based) index of the item to extract. Since we only had one top-level menu item, a value of zero gets passed in.
Finally, we make a call to the TrackPopupMenu()
method to display the menu to the user. The first parameter in this method call is a series of flags used to specify where the menu shows up in relation to the mouse cursor, as well as what buttons can be used to select the menu items. The screen position may only be one of the following:
TPM_CENTERALIGN
- Centers the menu horizontally relative to the coordinate specified by x.TPM_LEFTALIGN
- Positions the menu so that its left side is aligned with the coordinate specified by x.TPM_RIGHTALIGN
- Positions the menu so that its right side is aligned with the coordinate specified by x.
The mouse button flag can be any combination of the following two values:
TPM_LEFTBUTTON
- Causes the menu to track the left mouse button.TPM_RIGHTBUTTON
- Causes the menu to track the right mouse button.
The next two parameters in the TrackPopupMenu()
call specify the horizontal and vertical locations of the menu (the horizontal position depending on the flag used in the first parameter). Next comes a pointer to the window that owns the popup menu. Since we want our messages to be routed to our top-most parent, we use a call to AfxGetMainWnd()
to get the top level window. The final parameter defines a CRect
in which the user can click without dismissing the popup menu. A value of NULL here will do the right thing: clicking outside of the menu will dismiss it.