Managing Context Menus

Changing the context menu when the user right-clicks on your tool palette is relatively simple. AcadToolImpl derives from IAcadToolContextMenu. However, its method definitions simply return E_NOTIMPL to avoid linker errors. Therefore, you must override the interface's two methods: IAcadToolContextMenu::Customize() and IAcadToolContextMenu::InvokeMenuCommand().

Because you can change the context menu only at runtime, you must define your menu commands dynamically. The framework calls your implementation of IAcadToolContextMenu::Customize() when the user right-clicks any palette in the Tool Palettes window. It passes you the menu handle, ID of the current palette, upper and lower bounds for menu command IDs, and a context flag. Your implementation should do the following:

On each right-click event, the framework calls the Customize() function on your stock tool COM object. If your application allows a user to define your stock tool multiple times, you may inadvertently re-populate the context menu for each of those definitions. To guard against this scenario, verify that your stock tool's GUID is not in the stock tool catalog before creating a new instance.

If the user chooses a command from the menu, the InvokeMenuCommand() method is called on every tool object that implements IAcadToolContextMenu. When InvokeMenuCommand() is called, the framework passes you the selected menu item's command ID, the tool palette ID, and the window handle. If the command ID matches one of yours, and if the tool palette ID and window handle are acceptable, you execute appropriate actions.

The following procedures use MFC menu objects to append three menu items to the context menu.

To set up your IAcadToolContextMenu implementation

  1. In the simpleTool.h file, add the following pre-processor definition:
    #define MAX_MENU_ENTRIES 3
    
  2. In the simpleTool.cpp file, add the following static array for storing IDs that you use:
    static UINT m_nMenuIds[MAX_MENU_ENTRIES] = {0,0,0};
    
  3. In the CSimpleTool class declaration, declare prototypes for the two IAcadToolContextMenu methods:
    // IAcadToolContextMenu members
    STDMETHOD(Customize)(/* [in] */ int nContextFlag,
    	/* [in] */ DWORD hMenu,
    	/* [in] */ UINT idCmdFirst,
    	/* [in] */ UINT idCmdLast,
    	/* [in] */ GUID *pPaletteId,
    	/* [retval][out] */ DWORD *pFlag);
    STDMETHOD(InvokeMenuCommand)(/* [in] */ UINT idCmd,
    	/* [in] */ GUID *pPaletteId,
    	/* [in] */ DWORD hWnd,
    	/* [retval][out] */ DWORD *pFlag);
    
  4. In the SimpleTool.cpp file, add blank implementation prototypes for the Customize() and InvokeMenuCommand() methods.

    You now are ready to implement your context menu functionality.

To implement the Customize() method

  1. In your Customize() implementation body, check that you aren't trying to add more menu items than the framework allows:
    int maxMenu = (idCmdLast - idCmdFirst) < MAX_MENU_ENTRIES ?     (idCmdLast - idCmdFirst) : MAX_MENU_ENTRIES;
    
  2. Use the pPaletteId* argument to get the current tool palette's name. You can use the AcTcManager object to retrieve the palette. The following code demonstrates this.
    AcTcPalette* pPalette = 
        (AcTcPalette*)(AcTcGetManager()->FindItem(*pPaletteId));
    ASSERT(pPalette != NULL);
    TCHAR pszString[1024]; // arbitrary buffer size; not                       // limited by the framework
    pPalette->GetName(pszString, 1024);
    
  3. Compare the palette's name to that of your palette. If you find it does not match, re-initialize your menu ID array entries to 0 and return:
    if (_tcscmp(pszString, "SimplePalette")!= 0)
    {
        for (i=0; i < maxMenu ; i++)
        {
            m_nMenuIds[i] = 0;
        }
        return E_NOTIMPL;
    }
    
  4. Save three command IDs from the given range, using the maximum menu item limit determined in step 1:
    int i;
    for (i=0; i < maxMenu ; i++)
    {
        m_nMenuIds[i] = idCmdFirst + i;
    }
    
  5. Create a new CMenu object:
    CMenu* menu = new CMenu;
    
  6. Attach the CMenu object to the context menu, using the hMenu parameter and the CMenu::Attach() function:
    menu->Attach(HMENU(hMenu);
    
  7. Add three menu commands, using the selected command IDs:
    menu->InsertMenu(-1, MF_BYPOSITION, m_nMenuIds[0], "Menu&1");
    menu->InsertMenu(-1, MF_BYPOSITION, m_nMenuIds[1], "Menu&2");
    menu->InsertMenu(-1, MF_BYPOSITION, m_nMenuIds[2], "Menu&3");
    
  8. Detach the CMenu object and delete it:
    menu->Detach();
    delete menu;
    

    Here is the finished Customize() method override:

    STDMETHODIMP CSimpleTool::Customize(/* [in] */ int nContextFlag,
    					/* [in] */ DWORD hMenu,
    					/* [in] */ UINT idCmdFirst,
    					/* [in] */ UINT idCmdLast,
    					/* [in] */ GUID *pPaletteId,
    					/* [retval][out] */ DWORD *pFlag)
    {
    AcTcPalette* pPalette = 
        (AcTcPalette*)(AcTcGetManager()->FindItem(*pPaletteId));
    ASSERT(pPalette != NULL);
    TCHAR pszString[128];
    pPalette->GetName(pszString, 128);
    int i=0;
    int maxMenu = (idCmdLast - idCmdFirst) < MAX_MENU_ENTRIES ?
        (idCmdLast - idCmdFirst) : MAX_MENU_ENTRIES;
    if (_tcscmp(pszString, "SimplePalette")!= 0)
    {
        // Zero-out any existing IDs
        for (i=0; i < maxMenu ; i++)
        {
            m_nMenuIds[i] = 0;
        }
        return E_NOTIMPL;
    }
    for (i=0; i < maxMenu ; i++)
        m_nMenuIds[i] = idCmdFirst + i;
    CMenu* menu = new CMenu;
    if (menu->Attach(HMENU(hMenu))) 
    {
        menu->InsertMenu(-1, MF_BYPOSITION, m_nMenuIds[0],         "Menu&1");
        menu->InsertMenu(-1, MF_BYPOSITION, m_nMenuIds[1],         "Menu&2");
        menu->InsertMenu(-1, MF_BYPOSITION, m_nMenuIds[2],         "Menu&3");
        menu->Detach();
    }
    delete menu;
    return S_OK;
    }
    

    The last Customize() function parameter, pFlag, is an [out,retval] parameter, which can be used to suppress the display of the context menu. See the ObjectARX Reference for more information on using this parameter.

    The final step in implementing context menu functionality is to override the InvokeMenuCommand() method. For this simple case, you merely compare the idCmd argument's value to the menu IDs that you saved in the Customize() method. If you find one of your IDs, take appropriate action. The following code implements InvokeMenuCommand().

    STDMETHODIMP CSimpleTool::InvokeMenuCommand(/* [in] */ UINT idCmd,
    	    /* [in] */ GUID *pPaletteId,
    	    /* [in] */ DWORD hWnd,
    	    /* [retval][out] */ DWORD *pFlag)
    {
    if (idCmd == m_nMenuIds[0])
        ::AfxMessageBox("Menu1 chosen");
    else if (idCmd == m_nMenuIds[1])
        ::AfxMessageBox("Menu2 chosen");
    else if (idCmd == m_nMenuIds[2])
        ::AfxMessageBox("Menu3 chosen");
    return S_OK;
    }