Menu

Issue with context menu on popup menu

Help
2016-09-15
2016-09-17
  • Daniel Pantea

    Daniel Pantea - 2016-09-15

    Hi David,
    I'm trying to create a context menu when I right-click another popup menu. The second menu displays fine but, after closing, the first one freezes and also the item which was initially right-clicked gets disabled and its text shifts to left. The initial menu is not responding until user cliks outside which closes it.
    I suspect it has to do with owner drawing of main menus or some other menu hooks, however I didn't find a solution yet.

    Here's an example: I use the Frame sample from latest commit and there I do the following modifications:

    • in targetver.h select "For Windows 2000" so #define WINVER 0x0500, because only then WM_MENURBUTTONUP is defined.
    • in Mainfrm.h add these at the end:
        virtual LRESULT WndProc(UINT uMsg, WPARAM wParam, LPARAM lParam);
        virtual BOOL PreTranslateMessage(MSG& Msg);
        BOOL    OnMenuRButtonUp(WPARAM wParam, LPARAM lParam); // to handle WM_MENURBUTTONUP
    
    private:
        // show first popup menu when user right-click the window
        void createFirstPopup();
        // show the 2nd popup when user right-click the 1st menu
        void createSecondPopup();
    
        CView m_View;
        CDoc m_Doc;
        HMENU firstMenu; // to know if the first menu is active
    };
    
    #endif //MAINFRM_H
    
    • in Mainfrm.cpp add these at the end:
    void CMainFrame::createFirstPopup()
    {
        TRACE("Frame createFirstPopup-entering\n");
        CPoint pt = GetCursorPos();
        // Create the menu
        CMenu Popup;
        Popup.CreatePopupMenu();
        // Add some menu items
        Popup.AppendMenu(MF_STRING, 101, _T("Menu1 Item &1"));
        Popup.AppendMenu(MF_STRING, 102, _T("Menu1 Item &2"));
        Popup.AppendMenu(MF_STRING, 103, _T("Menu1 Item &3"));
        firstMenu = Popup.GetHandle();
    
        // Display the popup menu
        UINT sel = Popup.TrackPopupMenu(TPM_RETURNCMD, pt.x, pt.y, *this);
        firstMenu = 0;
        if (sel == 101)
        {
            TRACE("Frame createFirstPopup-exit 101\n");
        }
        else
        {
            TRACE("Frame createFirstPopup-exit other\n");
        }
    }
    
    void CMainFrame::createSecondPopup()
    {
        TRACE("Frame createSecondPopup-entering\n");
        CPoint pt = GetCursorPos();
        // Create the menu
        CMenu Popup;
        Popup.CreatePopupMenu();
        // Add some menu items
        Popup.AppendMenu(MF_STRING, 201, _T("Menu2 Item &1"));
        Popup.AppendMenu(MF_STRING, 202, _T("Menu2 Item &2"));
        Popup.AppendMenu(MF_STRING, 203, _T("Menu2 Item &3"));
    
        // Display the popup menu
        UINT sel = Popup.TrackPopupMenu(TPM_RETURNCMD | TPM_RECURSE, pt.x, pt.y, *this);
        if (sel == 201)
        {
            TRACE("Frame createSecondPopup-exit 201\n");
        }
        else
        {
            TRACE("Frame createSecondPopup-exit other\n");
        }
    }
    
    BOOL CMainFrame::OnMenuRButtonUp(WPARAM wParam, LPARAM lParam)
    {
        UINT uPos = (UINT)wParam;
        HMENU hmenu = (HMENU)lParam;
        TRACE("Frame OnMenuRButtonUp-entering\n");
        if (hmenu == firstMenu)
        {
            TRACE("Frame OnMenuRButtonUp-good menu\n");
            if (uPos >= 1 && uPos <= 2) // some dummy filtering
            {
                TRACE("Frame OnMenuRButtonUp-showing second menu\n");
                createSecondPopup();
                return 1;
            }
        }
        return 0;
    }
    
    LRESULT CMainFrame::WndProc(UINT uMsg, WPARAM wParam, LPARAM lParam)
    {
        switch (uMsg)
        {
        case WM_CONTEXTMENU:
        // case WM_RBUTTONUP: // this works in PreTranslateMessage(), but didn't come here...
            if (firstMenu == 0)
            {
                TRACE("Frame WndProc-WM_CONTEXTMENU - handling now\n");
                createFirstPopup();
                TRACE("Frame WndProc-WM_CONTEXTMENU - handled\n");
            }
            else
            {
                TRACE("Frame WndProc-WM_CONTEXTMENU - ignored\n");
            }
            break;
        // this doesn't come in PreTranslateMessage()!
        case WM_MENURBUTTONUP:
            TRACE("Frame WndProc-WM_MENURBUTTONUP - handled now\n");
            return OnMenuRButtonUp(wParam, lParam);
            break;
        default:
            break;
        }
        // pass unhandled messages on for default processing
        return WndProcDefault(uMsg, wParam, lParam);
    }
    
    BOOL CMainFrame::PreTranslateMessage(MSG& Msg)
    {
        switch (Msg.message)
        {
            // this doesn't come to WndProc(), however we handle WM_CONTEXTMENU there...
        case WM_RBUTTONUP:
            TRACE("Frame PreTranslateMessage-WM_RBUTTONUP, comes only here, but we handle WM_CONTEXTMENU instead in WndProc().\n");
            break;
        case WM_CONTEXTMENU:
            TRACE("Frame PreTranslateMessage-WM_CONTEXTMENU not handled here...\n");
            break;
            // this doesn't come here but goes to WndProc(), so it's handled there...
        case WM_MENURBUTTONUP:
            TRACE("Frame PreTranslateMessage-WM_MENURBUTTONUP not comming here!\n");
            break;
        default:
            break;
        }
        // Override this function if your class requires input messages to be
        // translated before normal processing. Function which translate messages
        // include TranslateAccelerator, TranslateMDISysAccel and IsDialogMessage.
        // Return TRUE if the message is translated.
        return CWnd::PreTranslateMessage(Msg);
    }
    

    If you have time can you please check if it's an win32++ framework issue or if it's not used properly by me? Or if you saw similar issue in the past, kindly please let me know. It's a bit hard to debug because as soon the menu looses the focus it gets closed and if you have some hints how to debug or what to trace this please let me know too.
    Thank you,
    Daniel

     
  • David

    David - 2016-09-17

    Hi Daniel,

    Thanks for your question. Yes, this issue was caused by CFrame's handling of owner draw for menus. To be honest, I hadn't considered the possibility of initiating a popup menu from within a popup menu. You can download the latest code from SourceForge which fixes this.

    Here are some hints on debugging or tracing messages from popup menus ...

    Regarding WM_RBUTTONUP:
    The WM_RBUTTONUP message is posted when the user releases the right mouse button while the cursor is in the client area of a window. If the cursor is over the view window, the view window's WndProc will see the WM_RBUTTONUP message, not the CMainFrame's WndProc.

    CMainFrame's PreTranslateMessage sees the message because PreTranslateMessage walks up the chain of parent windows. It is picking up the message from the view window.

    Regarding WM_CONTEXTMENU:
    This message goes to the window specified by TrackPopupMenu, which is CMainFrame in this case.

    PreTranslateMessage only handles mouse and keyboard messages, so it won't see the WM_CONTEXTMENU message.

    Additional notes:
    1) Microsoft's Spy++ utility is the best tool for troubleshooting window messages. It ships with Microsoft's Visual Studio Community 2013/2015 which is free.

    2) While the popup menu is active, it handles its own messages in a private message loop. It sends some messages to the window specified by TrackPopupMenu, but to handle all its messages you would need to install a message hook.

    3) We shouldn't use the PreTranslateMessage function to handle messages, that's not its intended purpose. Messages should always be handled in the window's WndProc. PreTranslateMessage is intended to be used when a message needs to be pre-handled before WndProc sees it.

    Best regards,
    David

     
  • Daniel Pantea

    Daniel Pantea - 2016-09-17

    Thanks a lot David, works perfect now!
    Really appreciate the clear explanations and the debuging advices.
    Best regards,
    Daniel

     

Log in to post a comment.