c++windowswinapisystray

Why does my Windows Systray Application with Custom Class Work Without Calling DispatchMessage?


This is my first time posting here, so I apologize if my question is unclear or not well-structured. I'll do my best to explain my problem clearly.

I'm currently learning Windows application programming in C++, and I'm experimenting with creating a systray application. I have managed to create a system tray icon that, when right-clicked, shows a message box and exits the application.

However, after that, I'm trying to encapsulate the systray functionality within a custom class and encountered an interesting behavior that I don't fully understand.

My code:

I have broken down the functionality into three files:

main.cpp
int APIENTRY wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow)
{
    UNREFERENCED_PARAMETER(hPrevInstance);
    UNREFERENCED_PARAMETER(lpCmdLine);  

    std::unique_ptr<windowsSystray> driver = std::make_unique<windowsSystray>(hInstance, nullptr, L"Tree App");

    MSG msg;
    while (GetMessage(&msg, nullptr, 0, 0))
    {
        // Normally, we'd call DispatchMessage here, but this works without it.       
    }

    if (hAppMutex) CloseHandle(hAppMutex);
    return static_cast<int>(msg.wParam);
}
windowsSystray.h
class windowsSystray :
    public SystrayDriver
{
public:
    windowsSystray(HINSTANCE hInstance, HWND hWnd, const std::wstring& tooltipText);
    ~windowsSystray();

private:  
    void registerWindowClass();
    void createSystrayWindow();
    void unregisterWindowClass();
    
    void addIcon() override;
    void removeIcon() override;
    
    static LRESULT CALLBACK HandleMessage(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

    static NOTIFYICONDATA nidApp;
    static HMENU hPopMenu;    
    
    HINSTANCE hInst;
    HWND hWnd;
    std::wstring tooltipText;
};
windowsSystray.cpp
#include "windowsSystray.h"

NOTIFYICONDATA windowsSystray::nidApp = { 0 };

windowsSystray::windowsSystray(HINSTANCE hInstance, HWND hWnd, const std::wstring& tooltipText)
    : hInst(hInstance), hWnd(hWnd), tooltipText(tooltipText) {

    registerWindowClass();  
    createSystrayWindow();   
    addIcon();
}

windowsSystray::~windowsSystray() { 
    removeIcon();
    unregisterWindowClass();
}

void windowsSystray::registerWindowClass() {
    WNDCLASSEX wc;
    wc.cbSize = sizeof(WNDCLASSEX);
    wc.style = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc = HandleMessage;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = hInst;
    wc.hIcon = LoadIcon(hInst, MAKEINTRESOURCE(IDI_SYSTRAY_ICON));
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    wc.lpszMenuName = MAKEINTRESOURCE(IDM_SYSTRAY);
    wc.lpszClassName = L"TreeSystrayClass";
    wc.hIconSm = LoadIcon(hInst, MAKEINTRESOURCE(IDI_SYSTRAY_ICON));
    RegisterClassEx(&wc);
}


void windowsSystray::createSystrayWindow() {    
    HICON hMainIcon = LoadIcon(hInst, (LPCTSTR)MAKEINTRESOURCE(IDI_SYSTRAY_ICON));
    hWnd = hWnd;
    hWnd = CreateWindow(L"TreeSystrayClass", L"treeCollector", WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInst, NULL);

    if (!hWnd) return;

    nidApp.cbSize = sizeof(NOTIFYICONDATA);                         // sizeof the struct in bytes 
    nidApp.hWnd = (HWND)hWnd;                                       //handle of the window which will process this app. messages 
    nidApp.uID = IDI_SYSTRAY_ICON;                                  //ID of the icon that willl appear in the system tray 
    nidApp.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP;               //ORing of all the flags 
    nidApp.hIcon = hMainIcon;                                       // handle of the Icon to be displayed, obtained from LoadIcon 
    nidApp.uCallbackMessage = WM_SYSTRAY;
}

void windowsSystray::unregisterWindowClass() {
    UnregisterClass(L"TreeSystrayClass", hInst);
}

void windowsSystray::addIcon() {
    Shell_NotifyIcon(NIM_ADD, &nidApp);
}

void windowsSystray::removeIcon() {
    Shell_NotifyIcon(NIM_DELETE, &nidApp);
}

LRESULT CALLBACK windowsSystray::HandleMessage(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {

    int wmId, wmEvent;
    //POINT lpClickPoint;

    switch (message)
    {

    case WM_SYSTRAY:
        switch (LOWORD(lParam))
        {
        case WM_RBUTTONDOWN:
            MessageBox(hwnd, TEXT("HERE1"), TEXT("Tree"), MB_OK);
            Shell_NotifyIcon(NIM_DELETE, &nidApp);
            DestroyWindow(hwnd);
        }
        break;
    case WM_COMMAND:
        wmId = LOWORD(wParam);
        wmEvent = HIWORD(wParam);
        // Parse the menu selections:
        switch (wmId)
        {
        /*case IDC_MENU_ABOUT:
            MessageBox(hwnd, TEXT("HERE2"), TEXT("Tree"), MB_OK);
            break;*/
        default:
            return DefWindowProc(hwnd, message, wParam, lParam);
        }
        break;
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hwnd, message, wParam, lParam);
    }
    return 0;
}

The Issue I don’t understand:

When I encapsulate the systray functionality inside the windowsSystray class and run the app, it creates the icon and then stays in the GetMessage() loop, it works as expected even though I never call DispatchMessage() inside the main loop.

My Questions:

Why does the GetMessage() loop still work without the call to DispatchMessage()? Does Windows automatically call the message handler when I don’t call DispatchMessage() explicitly?

Is this behavior expected when using a custom class to manage the systray? Or am I missing something fundamental here?

Any insights or clarifications would be greatly appreciated!

This is a second version of testing the systray functionality, just trying to mix the winAPI with POO. The first one had no class and worked just the same. When running, it does not cost memory even though there is nothing inside the getmessage while.


Solution

  • Per the GetMessage documentation:

    https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getmessage

    Retrieves a message from the calling thread's message queue. The function dispatches incoming sent messages until a posted message is available for retrieval.

    ...

    During this call, the system delivers pending, nonqueued messages, that is, messages sent to windows owned by the calling thread using the SendMessage, SendMessageCallback, SendMessageTimeout, or SendNotifyMessage function. Then the first queued message that matches the specified filter is retrieved. The system may also process internal events.

    So, yes, GetMessage() can dispatch certain messages without you having to call DispatchMessage(). However, a proper message loop should dispatch posted window messages explicitly.