c++winapiwndprocsetwindowshookex

Create event handler at runtime without using WndProc win32 c++


While using C#, it was easy before to create an event handler at runtime like:

Button button1 = new button1();

button1.click += Button_Click(); //Create handler 
button1.click -= Button_Click(); //Remove handler 

public void Button_Click()
{
    //Button clicked
}

But in win32 I am stuck with WndProc callback where I have to handle all events. I want to create a handler for specific message and attach it to specific void.

Currently, I am using WndProc to catch WM_CREATE message and draw controls:

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
        case WM_CREATE:
            Draw(hWnd); 
            break; 
        case WM_DESTROY:
            PostQuitMessage(0);
            break;
        default:
            return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}

void Draw(HWND hWnd)
{
    HWND button4 = CreateWindow(L"button", L"button4", WS_CHILD | WS_VISIBLE , 329, 118, 112, 67, hWnd, (HMENU)1001, hInst, NULL);
    HWND button3 = CreateWindow(L"button", L"button3", WS_CHILD | WS_VISIBLE , 212, 118, 112, 67,         
    ...
}

But I want to create or remove event handler instead of using WndProc at runtime something like:

AddHandler WM_CREATE , Draw(hWnd);
DelHandler WM_CREATE , Draw(hWnd);

What I have tried ?

The problem with SetWindowsHookEx is that its handles entire messages like WndProc. I don't want an handler that handles entire window messages and skip some of them. May be this can create performance or memory leak issue's.

Edit: Implemented example from answer:

#include <unordered_map>
using msgHandler = LRESULT(*)(HWND, UINT, WPARAM, LPARAM);
std::unordered_map<UINT, msgHandler> messageHandlers;

LRESULT handleCreate(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    //Draw some buttons to see whether event WM_CREATE called or not
    HWND button4 = CreateWindow(L"button", L"button4", WS_CHILD | WS_VISIBLE, 329, 118, 112, 67, hWnd, (HMENU)1001, hInst, NULL);
    HWND button3 = CreateWindow(L"button", L"button3", WS_CHILD | WS_VISIBLE, 212, 118, 112, 67, hWnd, (HMENU)1002, hInst, NULL);
    HWND button2 = CreateWindow(L"button", L"button2", WS_CHILD | WS_VISIBLE, 329, 46,  112, 67, hWnd, (HMENU)1003, hInst, NULL);
    HWND button1 = CreateWindow(L"button", L"button1", WS_CHILD | WS_VISIBLE, 212, 46,  112, 67, hWnd, (HMENU)1004, hInst, NULL);
    return 0;
}

LRESULT handleClose(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    //Quit form
    PostQuitMessage(0);           
    return 0;
}
void AddHandler() 
{
    messageHandlers[WM_CREATE] = handleCreate;
    messageHandlers[WM_DESTROY] = handleClose;
}

void DelHandler()
{
   messageHandlers.erase(WM_CREATE);
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    auto handler = messageHandlers.find(msg);
    if (handler != messageHandlers.end()) return handler->second(hWnd, msg, wParam, lParam);
    return DefWindowProc(hWnd, msg, wParam, lParam);
}

BOOL InitInstance(HINSTANCE hInstance, int nCmdShow){
    AddHandler();
    //DelHandler();
    ...


Solution

  • The message ID is just an unsigned integer, so there's not really anything special about it. Although the giant switch statement is one common way to handle messages, you can do things quite differently if you choose. To support dynamic insertion/deletion of handlers, one possibility would be to use an std::unordered_map:

    // a message handler receives the normal parameters:
    using msgHandler = LRESULT(*)(HWND, UINT, WPARAM, LPARAM);
    
    // a map from message numbers to the handler functions:
    std::unordered_map<UINT, msgHandler> messageHandlers;
    
    // A couple of message handler functions:
    LRESULT handleCreate(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
        // ...
    }
    
    LRESULT handleDraw(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
       // ...
    }
    
    // register them to handle the appropriate messages:
    messageHandlers[WM_CREATE] = handleCreate;
    messageHandlers[WM_PAINT] = handleDraw;
    
    // and then our (now really tiny) window proc that uses those:
    LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
    {
        auto handler = messageHandlers.find(msg);
        if (handler != messageHandlers.end())
            return handler->second(hWnd, msg, wParam, lParam);
        return DefWindowProc(hWnd, msg, wParam, lParam);
    }
    

    Since we're just storing pointers to functions in an std::unordered_map, adding, finding, or deleting a handler all just use the normal operations for adding, finding, or deleting something in an std::unodered_map (e.g., messageHandlers.erase(WM_CREATE); to erase the WM_CREATE handler from the map).

    If you want to get more elaborate with this, you can create specific types for handling different messages, so (for example) one that doesn't receive anything meaningful in its lParam simply won't receive an lParam at all, while another that receives two things "smooshed" together, one in the low word of lParam, and the other in the high word of lParam can receive them broken apart into two separate parameters. But that's a lot more work.

    You might also want to look for WindowsX.h, a header Microsoft provides (or at least used to provide) in the SDK that handles mapping a little like I've outlined above (the latter version, where each handler receives parameters that represent the logical data it receives, instead of the WPARAM and LPARAM used to encode that data.