Multiple Selection Menu – Part 2

The previous article showed how tu use a WH_MSGFILTER hook in order to make a menu with multiple selection. Now, let’s see an MFC-extension class that implements a context pop-up menu with multiple selection.

CMultiSelPopupMenu class

class CMultiSelPopupMenu : public CMenu
{
// Construction
public:
   CMultiSelPopupMenu(UINT nIDResource, UINT nSubMenu);

// Operations
public:
   void MDX_Check(CDataExchange* pDX, WORD wItemID, BOOL& bCheck);
   BOOL Show(UINT nFlags, CPoint point, CWnd* pWnd);

// ... 
};

CMultiSelPopupMenu class is derived from CMenu MFC class and has three public methods:

  1. CMultiSelPopupMenu::CMultiSelPopupMenu constructs a CMultiSelPopupMenu object.
    Parameters:
    • nIDResource – resource identifier of the menu;
    • nSubMenu – the index of pop-up sub-menu to be shown.
  2. CMultiSelPopupMenu::MDX_Check looks very similar to DDX_Check global function from the MFC framework. While DDX_Check is used to manage data exchange between check box controls and window class objects, CMultiSelPopupMenu::MDX_Check manages data exchange between multiple-selection menus and window class objects.
    Parameters:
    • pDX – a pointer to a CDataExchange object; it has the same purpose as in DDX_Check;
    • wItemID – the identifier of the menu item;
    • bCheck – a reference to a member variable of the window class object with which data is exchanged.
  3. CMultiSelPopupMenu::Show shows a pop-up context menu. It is similar to CMenu::TrackPopupMenu, except that shown menu allows multipe selection.
    Parameters:
    • nFlags – specifies screen-position and mouse-position flags; see TrackPopupMenu documentation for more details;
    • point – screen coordinates of the pop-up menu;
    • pWnd – the window that owns the pop-up menu.

You can get more usage and implementation details from the attached demo project. I think, the source code can tell more than any other explanations. However, if something isn’t very clear, please do not hesitate to leave a reply.

Demo project

Download: Context_Multiple_Selection_Menu.zip (29 KB)

The demo project is a simple MFC application that shows a multiple-selection context menu when the user right-clicks in the client area of the window. Click on items to check/uncheck the desired ones. Finally, to dismiss the menu just click outside.

Multiple-selection context menu

See also

Multiple Selection Menu – Part 1

Introduction

In some applications we can find menus which allow multiple selection. One example is the pop-up menu shown by Microsoft Visual Studio for adding or removing toolbar buttons.

VS Add or Remove Buttons Menu

We’ll try to do something similar in our own MFC application.

Finding a solution

By default, a native Windows pop-up menu is dismissed when the user selects an item. I could not find in the documentation any flag, message or notification that allows changing this behavior. So, one solution may be using a hook. On the first site, may be tempted to use a CBT hook, as shown in a previous article “AfxMessageBox with Auto-close” (see the link, below). That’s because a pop-up menu is a window itself (of pre-defined class “#32768”).
However, easier and better for our purpose is using a message filter hook. This type of hook can be installed by passing WH_MSGFILTER value in the first parameter of SetWindowsHookEx function.

Using an application-wide WH_MSGFILTER hook

Next example sets a WH_MSGFILTER hook in application’s main thread class. The hook procedure processes and filters the messages occurred as a result of menu input.

// Demo.h : main header file for the Demo application
//...

struct MSGFILTER_HOOK_INFO
{
   HHOOK hHook;          // hook handle
   HMENU hMenu;          // selected menu handle
   WORD wMenuItemID;     // selected menu item identifier
};
// ...

class CDemoApp : public CWinApp
{
   // ...
// Overrides
public:
   virtual BOOL InitInstance();
   virtual int ExitInstance();

// Implementation
private:
   static MSGFILTER_HOOK_INFO m_hookInfo;
   static LRESULT CALLBACK MessageProc(int nCode, WPARAM wParam, LPARAM lParam);
   static void _HandleMenuMessage(MSG* pMSG);
};
// Demo.cpp : Defines the class behaviors for the application.
//...

MSGFILTER_HOOK_INFO CDemoApp::m_hookInfo;
// ...

BOOL CDemoApp::InitInstance()
{
   // Installs WH_MSGFILTER hook
   m_hookInfo.hHook = SetWindowsHookEx(WH_MSGFILTER, // hook type 
      &CDemoApp::MessageProc,                        // hook procedure
      NULL,                                          // module handle
      GetCurrentThreadId());                         // current thread ID 

   ASSERT(NULL != m_hookInfo.hHook);
   // ...

   return TRUE;
}

// WH_MSGFILTER hook procedure
LRESULT CALLBACK CDemoApp::MessageProc(int nCode, WPARAM wParam, LPARAM lParam)
{
   switch(nCode)
   {
   case MSGF_MENU: // message is for a menu
      {
         MSG* pMSG = (MSG*)lParam;
         _HandleMenuMessage(pMSG);
      }
      break;
   }
   return ::CallNextHookEx(m_hookInfo.hHook, nCode, wParam, lParam);
}

// handles hooked menu messages
void CDemoApp::_HandleMenuMessage(MSG* pMSG)
{
   switch(pMSG->message)
   {
   case WM_MENUSELECT:
      // keep in mind selected menu handle and selected item identifier
      m_hookInfo.wMenuItemID = LOWORD(pMSG->wParam);
      m_hookInfo.hMenu = (HMENU)pMSG->lParam;
      break;
   case WM_LBUTTONUP:
      {
         switch(m_hookInfo.wMenuItemID)
         {
         case ID_VIEW_TOOLBAR:
         case ID_VIEW_STATUS_BAR:
         // cases for other menu command IDs...
            {
               // toggle check item
               MENUITEMINFO menuItemInfo = {0};
               menuItemInfo.cbSize = sizeof(MENUITEMINFO);
               menuItemInfo.fMask = MIIM_STATE;
               ::GetMenuItemInfo(m_hookInfo.hMenu, 
                                 m_hookInfo.wMenuItemID, FALSE, &menuItemInfo);
               if(MFS_CHECKED & menuItemInfo.fState) 
                  menuItemInfo.fState &= ~MFS_CHECKED;
               else
                  menuItemInfo.fState |= MFS_CHECKED;
               ::SetMenuItemInfo(m_hookInfo.hMenu, 
                                 m_hookInfo.wMenuItemID, FALSE, &menuItemInfo);

               // send command message to main window
               CWnd* pWnd = AfxGetMainWnd();
               ASSERT_VALID(pWnd);
               pWnd->SendMessage(WM_COMMAND, MAKEWPARAM( m_hookInfo.wMenuItemID, 0), 0);  

               // change to WM_NULL to prevent closing menu
               pMSG->message = WM_NULL;
            }
         }
      }
      break;
   case WM_LBUTTONDBLCLK:
      // just set WM_NULL to get rid of all default processing 
      pMSG->message = WM_NULL;
      break;
    }
}

int CDemoApp::ExitInstance()
{
   // Uninstalls WH_MSGFILTER hook
   if(NULL != m_hookInfo.hHook)
   {
      ::UnhookWindowsHookEx(m_hookInfo.hHook);
   }
   // ...
   return CWinApp::ExitInstance();
}

What’s next?

This article just demonstrates how to use a WH_MSGFILTER hook in order to make a menu with multiple selection. We may notice that using an application-wide hook and some hard-coded menu item IDs is not the best choice. What about if we have tens of menus with tens of menu items?
Well, in the next article we’ll implement an MFC-extension class that can serve our initial purpose in a much more flexible and object-oriented manner.