c++visual-c++-2010winapi

Notify Icon Receives WM_LBUTTONDBLCLK But Not WM_CONTEXTMENU


I added a notification icon to my dialog based application, and it received WM_LBUTTONDBLCLK when the icon is double clicked on, but it is not receiving WM_CONTEXTMENU when the icon is right clicked or when the icon is highlighted with the keyboard and the context menu key is pressed. I based my usage of the notification icon on the example in the Windows 7.1 SDK Samples. So, I have no idea where I'm going wrong or why this isn't working.

Note: If I change WM_CONTEXTMENU to WM_RBUTTONUP, it receives the event, but the cursor coordinates are wrong.

/******************************************************************************/
/* Menu Resource                                                              */
/******************************************************************************/
LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL
IDR_TRAYMENU MENU
{
    POPUP ""
    {
        MENUITEM "&Show Status Window", IDM__SHOW_STATUS_WINDOW
        MENUITEM "&About", IDM__ABOUT
        MENUITEM SEPARATOR
        MENUITEM "&Exit", IDM__EXIT
    }
}

/******************************************************************************/
/* WinMain()                                                                  */
/******************************************************************************/
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow )
{

    // ... code unrelated to icon

    // Enable Visual Styles
    InitCommonControls();

    // create the main dialog
    if( NULL == (hWnd=CreateDialog(hInstance,MAKEINTRESOURCE(IDD_MAINDLG),NULL,(DLGPROC)WndProc)) )
    {
        MessageBox( NULL, "Error creating the main dialog!", NULL, MB_OK | MB_ICONERROR );
        return -1;
    }

    // ... code unrelated to icon

    MSG msg;
    while( GetMessage(&msg,NULL,0,0) && IsWindow(hWnd) )
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return 0; 

}    
/******************************************************************************/
/* WndProc()                                                                  */
/******************************************************************************/
BOOL CALLBACK WndProc(HWND hWndDlg, UINT Message, WPARAM wParam, LPARAM lParam)
{

    switch(Message)
    {
        case WM_INITDIALOG:
        {

            // ... code unrelated to icon
            hIcon = (HICON)LoadImage( GetModuleHandle(NULL), MAKEINTRESOURCE(IDI_DDCMP), IMAGE_ICON, 16, 16, LR_DEFAULTSIZE );

            // Setup the system tray icon
            memset( &nid, 0, sizeof(NOTIFYICONDATA) );
            nid.cbSize = sizeof(NOTIFYICONDATA);
            nid.hWnd = hWndDlg;
            nid.uID = 0xDDC;
            nid.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP | NIF_SHOWTIP;
            nid.uCallbackMessage = WM_APP + 0xDDC;
            nid.hIcon = hIcon;
            strcpy( nid.szTip, "DDCMP Driver" );
            Shell_NotifyIcon( NIM_ADD, &nid );

            // ... code unrelated to icon

            return true;
        } break;

        case WM_APP + 0xDDC:
        {
            switch( LOWORD(lParam) )
            {
                case WM_CONTEXTMENU:
                {
                    MessageBox( hWndDlg, "This message box never shows up.", NULL, MB_OK | MB_SYSTEMMODAL );
                    HMENU hMenu = LoadMenu(GetModuleHandle(NULL),MAKEINTRESOURCE(IDR_TRAYMENU));
                    if( hMenu )
                    {
                        HMENU hSubMenu = GetSubMenu(hMenu,0);
                        if( hSubMenu )
                        {
                            SetForegroundWindow( hWndDlg );
                            POINT pt = { LOWORD(wParam), HIWORD(wParam) };
                            UINT uFlags = TPM_RIGHTBUTTON;
                            if( 0 != GetSystemMetrics(SM_MENUDROPALIGNMENT) )
                                uFlags |= TPM_RIGHTALIGN;
                            else
                                uFlags |= TPM_LEFTALIGN;
                            TrackPopupMenuEx( hSubMenu, uFlags, pt.x, pt.y, hWndDlg, NULL );

                        }
                        DestroyMenu( hMenu );
                    }
                } break;
                case WM_LBUTTONDBLCLK:
                    if( IsWindowVisible(hWndDlg) )
                        ShowWindow( hWnd, SW_HIDE );
                    else
                        ShowWindow( hWnd, SW_SHOW );
                    break;
            }
            return true;
        } break;

        case WM_CLOSE:
            ShowWindow( hWndDlg, SW_HIDE );
            break;

        case WM_DESTROY:
        case WM_QUIT:
        {
            Shell_NotifyIcon( NIM_DELETE, &nid );

            // ... code unrelated to icon

            return true;
        } break;
    }

    return false;
}

This is the WndProc from the Windows 7.1 SDK Sample

    LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    static HWND s_hwndFlyout = NULL;
    static BOOL s_fCanShowFlyout = TRUE;

    switch (message)
    {
    case WM_CREATE:
        // add the notification icon
        if (!AddNotificationIcon(hwnd))
        {
            MessageBox(hwnd,
                L"Please read the ReadMe.txt file for troubleshooting",
                L"Error adding icon", MB_OK);
            return -1;
        }
        break;
    case WM_COMMAND:
        {
            int const wmId = LOWORD(wParam);
            // Parse the menu selections:
            switch (wmId)
            {
            case IDM_LOWINK:
                ShowLowInkBalloon();
                break;

            case IDM_NOINK:
                ShowNoInkBalloon();
                break;

            case IDM_PRINTJOB:
                ShowPrintJobBalloon();
                break;

            case IDM_OPTIONS:
                // placeholder for an options dialog
                MessageBox(hwnd,  L"Display the options dialog here.", L"Options", MB_OK);
                break;

            case IDM_EXIT:
                DestroyWindow(hwnd);
                break;

            case IDM_FLYOUT:
                s_hwndFlyout = ShowFlyout(hwnd);
                break;

            default:
                return DefWindowProc(hwnd, message, wParam, lParam);
            }
        }
        break;

    case WMAPP_NOTIFYCALLBACK:
        switch (LOWORD(lParam))
        {
        case NIN_SELECT:
            // for NOTIFYICON_VERSION_4 clients, NIN_SELECT is prerable to listening to mouse clicks and key presses
            // directly.
            if (IsWindowVisible(s_hwndFlyout))
            {
                HideFlyout(hwnd, s_hwndFlyout);
                s_hwndFlyout = NULL;
                s_fCanShowFlyout = FALSE;
            }
            else if (s_fCanShowFlyout)
            {
                s_hwndFlyout = ShowFlyout(hwnd);
            }
            break;

        case NIN_BALLOONTIMEOUT:
            RestoreTooltip();
            break;

        case NIN_BALLOONUSERCLICK:
            RestoreTooltip();
            // placeholder for the user clicking on the balloon.
            MessageBox(hwnd, L"The user clicked on the balloon.", L"User click", MB_OK);
            break;


        // 
        //
        // As you can very plainly see, the Windows SDK Sample ONLY used WM_CONTEXTMNEU
        // 
        //

        case WM_CONTEXTMENU:
            {
                POINT const pt = { LOWORD(wParam), HIWORD(wParam) };
                ShowContextMenu(hwnd, pt);
            }
            break;
        }
        break;

    case WMAPP_HIDEFLYOUT:
        HideFlyout(hwnd, s_hwndFlyout);
        s_hwndFlyout = NULL;
        s_fCanShowFlyout = FALSE;
        break;

    case WM_TIMER:
        if (wParam == HIDEFLYOUT_TIMER_ID)
        {
            // please see the comment in HideFlyout() for an explanation of this code.
            KillTimer(hwnd, HIDEFLYOUT_TIMER_ID);
            s_fCanShowFlyout = TRUE;
        }
        break;
    case WM_DESTROY:
        DeleteNotificationIcon();
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hwnd, message, wParam, lParam);
    }
    return 0;
}

Solution

  • The notify icon has changed behaviour over the years. For reasons of compatibility with pre-existing code, you must opt-in to the new behaviour. If you don't opt-in then you don't get sent WM_CONTEXTMENU messages. Instead you have to respond to WM_RBUTTONUP. Even if you invoke the context menu from the keyboard, the system still sends WM_RBUTTONUP. You have to obtain the cursor position, in order to know where to show the menu, by calling GetCursorPos.

    You can opt in to the new behaviour (and WM_CONTEXTMENU) as described in the documentation, by calling Shell_NotifyIcon passing NIM_SETVERSION after the NIM_ADD call. Presumably the SDK sample you are looking at does this somewhere. My guess is that is what is missing from your code.

    The key extract from the documentation is in the remarks section:

    As of Windows 2000 (Shell32.dll version 5.0), Shell_NotifyIcon mouse and keyboard events are handled differently than in earlier Shell versions found on Microsoft Windows NT 4.0, Windows 95, and Windows 98. The differences include the following:

    • If a user selects a notify icon's shortcut menu with the keyboard, the Shell now sends the associated application a WM_CONTEXTMENU message. Earlier versions send WM_RBUTTONDOWN and WM_RBUTTONUP messages.
    • If a user selects a notify icon with the keyboard and activates it with the SPACEBAR or ENTER key, the version 5.0 Shell sends the associated application an NIN_KEYSELECT notification. Earlier versions send WM_RBUTTONDOWN and WM_RBUTTONUP messages.
    • If a user selects a notify icon with the mouse and activates it with the ENTER key, the Shell now sends the associated application an NIN_SELECT notification. Earlier versions send WM_RBUTTONDOWN and WM_RBUTTONUP messages.