HOME
Login
Change Info
Logout


TUTORIALS

C, C++
Win32
Java
Visual Basic
MFC
DCOM
Networking
C#
Perl
HTML
XML
ASP
PHP
Javascript
Other

DOWNLOADS
ITCLib
SourceVizor meets the notification, reporting, and admin needs of teams using Microsoft Visual SourceSafe.
Free 30-day trial!


Adding Context Menus to your Visual C++ / MFC Application

by Brian Scarboro

Questions, comments, suggestions - We want to hear from you! Comment on this article Get the Acrobat PDF File For This Article Get Adobe Acrobat Reader
Most downloads under 500KB


Introduction

Windows applications are increasingly making use of context menus in various parts of their GUIs. To be consistent with other Windows apps, and to meet your users’ expectations, context menus should have a place in your applications as well.

This article will show you how to easily add context menus to your Visual C++ application and will illustrate the process with a working example. I’ll also discuss the issues I encountered as I learned how to do this myself, so that you can be aware of these potential hurdles. The source for the entire example project (created with VC++ 5.0) is available for download.

Basics

The general process is simple. The three basic steps for creating a context menu are:

  • Create the menu resource(s)
  • Add a message handling function for WM_CONTEXTMENU
  • If the point clicked is in the desired area, create a CMenu object and call its member TrackPopupMenu

Example

The example project is just a simple dialog-based application, which has a tree control in the dialog (see Figure 1). For the example, I wanted to respond to right-clicks on items in the tree control. I’ll present the step by step process of creating the context menus and discuss what I did specifically for the example.


Figure 1 -- Dialog-based application

Step 1 -- Create the menu resource(s).

Using the resource editor, add a new menu. Type anything you want for the top level item in the menu. It doesn’t matter what you put there; it won’t be displayed anyway. In the popup menu items that appear below the top level item, create your context menu. See Figure 2.

For this example, I created two menus so that I could display two different context menus depending on where the mouse click occurred. One menu (IDR_MENU2) has three items (Delete, Command 2, and Command 3). I want to display this menu when clicking on a child item in the tree. The other menu (IDR_MENU1) has only one item (Delete), and will be displayed when clicking on a parent item in the tree. It would be equally valid to have one single menu resource with two top-level items and submenus instead of two separate menu resources.

You will, of course, need to wire the menu choices to actual methods if you want them to do anything. The easiest thing to do is to use Class Wizard to create handlers for the menu items. For the example, I created a handler for the Delete menu item, and it just removes the item from the tree.

Step 2 -- Add the WM_CONTEXTMENU message handler.

Using the Class Wizard, add a handler function for the WM_CONTEXTMENU message. That will result in a new method called OnContextMenu(CWnd* pWnd, CPoint point). The OnContextMenu() method will be called each time there is a right-click inside the application window.

Step 3 -- Implement the WM_CONTEXTMENU message handler.

This step is the real meat of this exercise. In the message handler, we must decide whether or not to display a context menu. If the method decides not to display the context menu, then we’ll pass the message through to the base class, so the WM_CONTEXTMENU message can be processed there.

One important thing to be aware of is the frame of reference for the point of the mouse click. The CPoint parameter coming into the method is in screen coordinates. We’ll have to convert it to other reference points for use in other methods. Keep in mind that the upper left corner of the display is considered (0, 0) and the coordinates increase as you go left to right and top to bottom.

The determination of whether or not a menu should be displayed is based on the location of the right mouse click. So you’ll need to convert that point to be relative to the window you're interested in. If you want to respond to right-clicks anywhere in the window, then you’ll need to convert the coordinates to be relative to the client area of the window. If you’re only interested in clicks inside a control in the window, then you’ll need the point to be relative to that control. For the example, we’re only interested in clicks inside the tree control. More specifically, we’re interested in clicks on an item in the tree control. If a tree item wasn’t clicked, then there’s no need to display the menu. We’ll just pass the message on to the base class. But to make that determination, we must first convert the coordinates so that they are relative to the tree control by using CWnd::ScreenToClient().

Now we can determine what item (if any) was clicked in the tree. Luckily, CTreeCtrl provides the HitTest() method that does just that. (Not all controls provide a HitTest method. You may have to write your own.) The UINT parameter you give it comes back with a bitmasked value that indicates where the hit occurred. See the documentation for CTreeCtrl::HitTest() for more information on the flags coming back in the UINT parameter. For the example, I only wanted to pop up the menu if the item’s label or image was clicked, so I checked to see if the bit corresponding to TVHT_ONITEM was set in the mask.

Now, assuming we’ve made it this far, and an item was clicked, we’re almost ready to display the menu. For the tree control, we have to explicitly select the item that was clicked, so that the menu’s message handler will work properly. Additionally, for the example, we have to determine which context menu to display. As stated above in Step 1, the menu displayed depends on whether the item clicked was a parent item or a leaf item. So create the menu and load it using the appropriate resource. Since we want to display the pop-up portion of the menu and not the top-level, we call GetSubMenu(0) to get a pointer to the pop-up menu.

The final step to display the menu is to call CMenu::TrackPopupMenu(). This method displays the menu and handles the selection. Check the documentation for details on parameters. So here's the finished OnContextMenu() method.

Summary

So, as you can see, adding context menus to your application is not difficult at all. Just by adding a new menu resource or two, and implementing a handler method for the WM_CONTEXTMENU message, you're well on your way to having functioning context menus. And with some attention to a couple of details like point coordinates and proper treatment of unprocessed messages, you've got it licked.

Terms

Context Menu -- The floating menu that pops up when you right-click an object
Client Area -- The internal area of a window that excludes the title bar and frame
Frame of Reference -- The point to which another point is relative can also be thought of as the frame of reference. In other words, if point A is relative to point B, then point B serves as the origin of point A's coordinate system.
Screen Coordinates -- Point coordinates that are relative to the upper-left corner of the screen

WM_CONTEXTMENU message handler


void CContextMenuDlg::OnContextMenu(CWnd* pWnd, CPoint screenPoint)
{
    // Make a copy and convert the point to be 
    // relative to the tree control  
    CPoint treePoint = screenPoint;
    m_tree.ScreenToClient(&treePoint);

    // Now get the item that was clicked (if any).
    UINT nFlags;
    HTREEITEM hItemClicked = m_tree.HitTest(treePoint, &nFlags);

    // If an item's label or bitmap was clicked, 
    // then we'll display the menu
    if ((NULL != hItemClicked) && (TVHT_ONITEM & nFlags))
    {
        // Select the item
        m_tree.SelectItem(hItemClicked);

        // Get the menu appropriate to the item clicked
        CMenu menu;
        if (m_tree.ItemHasChildren(hItemClicked))
        {
            // Parent item
            menu.LoadMenu(IDR_MENU1);
        }
        else
        {
            // Child item
            menu.LoadMenu(IDR_MENU2);
        }

        // Display the menu (use screen coords)
        menu.GetSubMenu(0)->TrackPopupMenu(
            TPM_LEFTALIGN | TPM_RIGHTBUTTON, screenPoint.x, 
            screenPoint.y, this);
    }
    else
    {
        // We're not going to display the menu, 
        // so pass the message to the base class
        CDialog::OnContextMenu(pWnd, screenPoint);
    }
}


Featured Article

An Introduction to C#
By Joey Mingrone

Register Today!


100% FREE

Members enjoy these benefits:
Access to ITI Downloads
Access to more articles and tutorials
Optional weekly newsletter
And more...

Click here to register
Or
Click here to log in



© 2008 Interface Technologies, Inc. All Rights Reserved
Questions or Comments? devcentral AT iticentral DOT com
PRIVACY POLICY