2

I am developing a console program and I have planned to add a tray icon for it. But it seems that only Win32 GUI program can do something caused by messages. (WndProc())

My message checking loop code snippet: (Independent sub thread)

void WCH_message_loop() {
    // Message loop.
    MSG msg;
    GetMessageW(&msg, nullptr, 0, 0);
    Myn = new MyNotify(msg.hwnd);
    MyC = new CTrayIcon();
    MyC -> CreateTray(msg.hwnd, LoadIconW(NULL, IDI_ERROR), WCH_IDM_OPENPIC, L"Web Class Helper");
    constexpr int ShowHide = 121;
    constexpr int PrintScreen = 122;
    RegisterHotKey(NULL, ShowHide, MOD_CONTROL, 'P');
    RegisterHotKey(NULL, PrintScreen, MOD_CONTROL, VK_DOWN); // Register "Ctrl + Down" hotkey.
    while (GetMessage(&msg, nullptr, 0, 0)) {
        if (msg.message == WM_HOTKEY) {
            switch (msg.wParam) {
                case ShowHide:
                    WCH_SetWindowStatus(!WCH_cmd_line);
                    break;
                case PrintScreen:
                    cout << endl;
                    WCH_command_list.clear();
                    WCH_command_list = WCH_split("pi");
                    WCH_pi();
                    MyC -> ChangeTray(L"Successfully printed screen");
                    break;
                default:
                    break;
            }
        }
        if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)) {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }
}

WndProc() function: (How could I bind it to something or make it take effect?)

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
    #ifdef _DEBUG
    WCH_printlog(WCH_LOG_STATUS_DEBUG, "Starting \"WndProc()\"");
    #endif
    switch (message) {
        case 1025:
            switch (lParam) {
                case WM_LBUTTONDOWN:
                    WCH_SetWindowStatus(true);
                    break;
                case WM_RBUTTONDOWN:
                    WCH_SetTrayIconMenu(hWnd);
                    break;
            }
            break;
        case WM_COMMAND:
            switch (LOWORD(wParam)) {
                case WCH_IDM_SHOWHIDE:
                    WCH_SetWindowStatus(!WCH_cmd_line);
                    break;
                case WCH_IDM_EXIT:
                    exit(0);
                    break;
                default:
                    return DefWindowProcW(hWnd, message, wParam, lParam);
                    break;
            }
        case WM_PAINT: {
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hWnd, &ps);
            EndPaint(hWnd, &ps);
            break;
        }
        case WM_DESTROY:
            PostQuitMessage(0);
            break;
        default:
            return DefWindowProcW(hWnd, message, wParam, lParam);
    }
    return 0;
}

Dependent code snippet:

class CTrayIcon {
public:
    CTrayIcon() {};
    ~CTrayIcon() {};
    BOOL CreateTray(HWND, HICON, UINT, LPCTSTR = L"Web Class Helper");
    BOOL ChangeTray(LPCTSTR, LPCTSTR = L"Web Class Helper", UINT = 3000);
    BOOL DeleteTray();
private:
    NOTIFYICONDATA m_Notify;
};

struct MyNotify {
    MyNotify(HWND hWND, WCHAR* Path = (WCHAR*)L"WCHs.ico", WCHAR* Title = (WCHAR*)L"Web Class Helper", UINT uID = 1) {
        NOTIFYICONDATA nID = {};
        nID.hIcon = (HICON)LoadImageW(NULL, Path, IMAGE_ICON, 0, 0, LR_LOADFROMFILE);
        wcscpy_s(nID.szTip, Title);
        nID.hWnd = hWND;
        nID.uID = uID;
        nID.uFlags = NIF_GUID | NIF_ICON | NIF_MESSAGE | NIF_TIP;
        nID.uCallbackMessage = 1025;
        Shell_NotifyIconW(NIM_ADD, &nID);
    }
};

ATOM WCH_RegisterClass(HINSTANCE hInstance, WCHAR* szWindowClass) {
    #ifdef _DEBUG
    WCH_printlog(WCH_LOG_STATUS_DEBUG, "Registering class");
    #endif
    WNDCLASSEXW wcex;
    wcex.cbSize = sizeof(WNDCLASSEX);
    wcex.style = CS_HREDRAW | CS_VREDRAW;
    wcex.lpfnWndProc = WndProc;
    wcex.cbClsExtra = 0;
    wcex.cbWndExtra = 0;
    wcex.hInstance = hInstance;
    wcex.hIcon = LoadIconW(hInstance, MAKEINTRESOURCEW(IDI_WCH));
    wcex.hCursor = LoadCursorW(nullptr, IDC_ARROW);
    wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    wcex.lpszMenuName = MAKEINTRESOURCEW(IDC_WCH);
    wcex.lpszClassName = szWindowClass;
    wcex.hIconSm = LoadIconW(wcex.hInstance, MAKEINTRESOURCEW(IDI_SMALL));
    return RegisterClassExW(&wcex);
}

How could I solve this problem? Thanks!

Now I don't know why does WndProc() do not work. (Maybe just in GUI program?)

But I don't want to convert it to GUI program.

My friend told me that console is actually a GUI window which already has a WndProc() function, so I can not add a WndProc() function more. But can I modify current WndProc() function?

Yuchen Ren
  • 287
  • 5
  • 13
  • 3
    [Message-Only Windows](https://learn.microsoft.com/en-us/windows/win32/winmsg/window-features#message-only-windows). – IInspectable May 30 '22 at 12:01
  • 1
    None of what you have posted looks like console program. Would expect `int _tmain(int argc, _TCHAR* argv[])` blah blah and then `Shell_NotifyIcon` somewhere in it. Are you trying to build windows GUI program as console program? – Öö Tiib May 30 '22 at 12:29
  • @ÖöTiib But after I use `Shell_NotifyIcon`, how could I run functions that cause by events? This is just a module that implement this feature. If there is a way to make GUI window like a console, I'm interested. – Yuchen Ren May 30 '22 at 12:47
  • But there are plenty such consoles ... mintty, babun, conemu, cmder etc. – Öö Tiib May 30 '22 at 13:02
  • @YuchenRen console apps have full access to the Win32 API. You don't need a GUI app to display tray icons. But you do need a window and a message loop in order to receive events from a tray icon. You can use a hidden message-only window for that purpose. On a side note, `GetMessageW()` is a blocking function, it waits for a message to arrive. Your 1st call at the top of `WCH_message_loop()` should be using `PeekMessage(PM_NOREMOVE)` instead. – Remy Lebeau May 30 '22 at 19:29
  • So I only can create a new message window and receive messages? How could I do that? Using multiple threads? – Yuchen Ren May 31 '22 at 01:33

2 Answers2

0

I solved this problem by my friend's suggestion but I don't know why.

Welcome to point out the key points.

Below is the final complete code snippet.

Overall situation variable:

HMENU WCH_hMenu;

WndProc() function:

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
    // Window processing module.
    NOTIFYICONDATA nid {};
    switch (message) {
        case WM_CREATE:
            #ifdef _DEBUG
            WCH_printlog(WCH_LOG_STATUS_DEBUG, "Entering \"WndProc()\": \"WM_CREATE\"");
            #endif
            nid.cbSize = sizeof(nid);
            nid.hWnd = hWnd;
            nid.uID = 0;
            nid.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP;
            nid.uCallbackMessage = WM_USER;
            nid.hIcon = (HICON)LoadImageW(NULL, L"WCHS.ico", IMAGE_ICON, 0, 0, LR_LOADFROMFILE);
            wcscpy(nid.szTip, WCH_window_title.c_str());
            Shell_NotifyIconW(NIM_ADD, &nid);
            WCH_hMenu = CreatePopupMenu();
            AppendMenuW(WCH_hMenu, MF_STRING, WCH_IDM_SHOWHIDE, L"Show");
            AppendMenuW(WCH_hMenu, MF_SEPARATOR, 0, NULL);
            AppendMenuW(WCH_hMenu, MF_STRING, WCH_IDM_EXIT, L"Quit");
            break;
        case WM_USER:
            if (lParam == WM_LBUTTONDOWN) {
                #ifdef _DEBUG
                WCH_printlog(WCH_LOG_STATUS_DEBUG, "Entering \"WndProc()\": \"WM_USER\" & \"WM_LBUTTONDOWN\"");
                #endif
                WCH_SetWindowStatus(true);
            } else if (lParam == WM_RBUTTONDOWN) {
                POINT pt;
                int xx;
                GetCursorPos(&pt);
                SetForegroundWindow(hWnd);
                xx = TrackPopupMenu(WCH_hMenu, TPM_RETURNCMD, pt.x, pt.y, NULL, hWnd, NULL);
                #ifdef _DEBUG
                WCH_printlog(WCH_LOG_STATUS_DEBUG, "Entering \"WndProc()\": \"WM_USER\" & \"WM_RBUTTONDOWN\" & \"xx = " + to_string(xx) + "\"");
                #endif
                if (xx == IDR_SHOW) {
                    #ifdef _DEBUG
                    WCH_printlog(WCH_LOG_STATUS_DEBUG, "Entering \"WndProc()\": \"WM_USER\" & \"WM_RBUTTONDOWN\" & \"IDR_SHOW\"");
                    #endif
                    WCH_SetWindowStatus(true);
                } else if (xx == IDR_QUIT) {
                    #ifdef _DEBUG
                    WCH_printlog(WCH_LOG_STATUS_DEBUG, "Entering \"WndProc()\": \"WM_USER\" & \"WM_RBUTTONDOWN\" & \"IDR_QUIT\"");
                    #endif
                    WCH_command_list.clear();
                    WCH_command_list.push_back("quit");
                    cout << endl;
                    exit(0);
                } else if (xx == 0) {
                    PostMessageW(hWnd, WM_LBUTTONDOWN, NULL, NULL);
                }
            }
            break;
        case WM_DESTROY:
            #ifdef _DEBUG
            WCH_printlog(WCH_LOG_STATUS_DEBUG, "Entering \"WndProc()\": \"WM_DESTROY\"");
            #endif
            Shell_NotifyIconW(NIM_DELETE, &nid);
            PostQuitMessage(0);
            break;
        default:
            if (message == RegisterWindowMessageW(L"TaskbarCreated")) {
                SendMessageW(hWnd, WM_CREATE, wParam, lParam);
            }
            break;
    }
    return DefWindowProcW(hWnd, message, wParam, lParam);
}

Message handling sub thread module:

void WCH_message_loop() {
    // Message loop.
    HWND hwnd {};
    MSG msg {};
    WNDCLASS wndclass {};
    wndclass.style = CS_HREDRAW | CS_VREDRAW;
    wndclass.lpfnWndProc = WndProc;
    wndclass.cbClsExtra = 0;
    wndclass.cbWndExtra = 0;
    wndclass.hInstance = NULL;
    wndclass.hIcon = LoadIconW(NULL, IDI_APPLICATION);
    wndclass.hCursor = LoadCursorW(NULL, IDC_ARROW);
    wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
    wndclass.lpszMenuName = NULL;
    wndclass.lpszClassName = WCH_window_title.c_str();
    if (!RegisterClassW(&wndclass)) {
        MessageBoxW(NULL, L"This program requires Windows NT!", WCH_window_title.c_str(), MB_ICONERROR);
        exit(0);
    }
    hwnd = CreateWindowExW(WS_EX_TOOLWINDOW, WCH_window_title.c_str(), WCH_window_title.c_str(), WS_POPUP, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, NULL, NULL);
    ShowWindow(hwnd, SW_HIDE);
    UpdateWindow(hwnd);
    while (GetMessageW(&msg, NULL, 0, 0)) {
        TranslateMessage(&msg);
        DispatchMessageW(&msg);
    }
}
Yuchen Ren
  • 287
  • 5
  • 13
-1

The correct way to do this is to create a GUI program with a hidden main window.

When your program starts, alloc a console and attach the standard streams.

Dúthomhas
  • 8,200
  • 2
  • 17
  • 39
  • 1
    Hmm. What if the parent process is a console app, you surely want this child to use the parent's console. The correct way is to run a separate thread with a message loop to handle the notification icon. – David Heffernan May 31 '22 at 06:52
  • Good point. You should write an answer. – Dúthomhas May 31 '22 at 16:59