0

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();
    ...

Gray Programmerz
  • 479
  • 1
  • 5
  • 22
  • One option is to write your own WndProc that keeps a table of handlers and what messages they are registered for. Another is to write a WndProc that raises an event for each message, and have everybody register for the events they want to handle. – Raymond Chen Jan 05 '21 at 05:34
  • I require first option that tries to registered specific messages. Can you please guide me more ? – Gray Programmerz Jan 05 '21 at 07:10
  • In this example c# event handler is created at compile time. `//Create handler` and `//Remove handler` and wrong. – user7860670 Jan 05 '21 at 07:53
  • Maybe "raw" win32 is not what you need. There are other options for C/C++/Native Windows programming. For example, you could try MFC https://learn.microsoft.com/en-us/cpp/mfc/mfc-desktop-applications it's closer to .NET events or at least study it to get informations. – Simon Mourier Jan 05 '21 at 07:54
  • [This answer](https://stackoverflow.com/questions/65573570/create-event-handler-at-runtime-without-using-wndproc-win32-c#65573769) fills in the details of option 1. – Raymond Chen Jan 05 '21 at 13:37
  • Thanks raymond. @SimonMourier MFC is wrapper around win32. I want to write native windows programs from scratch. So that's why I prefer using win32 directly. – Gray Programmerz Jan 06 '21 at 17:42

2 Answers2

2

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.

Jerry Coffin
  • 476,176
  • 80
  • 629
  • 1,111
  • what `return handler->second` is used for ? And how do you remove handler once created using `messageHandlers[WM_CREATE]` ? – Gray Programmerz Jan 05 '21 at 07:10
  • @GrayProgrammerz: the `find` finds the handler for a particular message. The return value is an iterator into the map, so `handler->first` gives you the message ID you just looked up, and `handler->second` gives you the (pointer to) the function you registered for that message. The `return handler->second(...);` invokes that function, retrieves whatever value it returns (if any) and passes it back to the caller. – Jerry Coffin Jan 05 '21 at 07:35
  • As for "how do you remove handles": The point is that the `messageHandlers` records which handlers are in charge of which messages. If you want to modify the handlers, modify the `messageHandlers`. To add a handler, add it to the `messageHandlers`. To remove one, remove it from the `messageHandlers`. To search for a handler, search in the `messageHandlers`. And so on. – Raymond Chen Jan 05 '21 at 13:39
  • :) Thanks all. I tried this code and I solved 1 error and second is one is hard. Please see : https://i.ibb.co/tYhbQvX/Capture.png – Gray Programmerz Jan 06 '21 at 16:52
  • @GrayProgrammerz: Those are assignment statements, so they need to be inside of a function. – Jerry Coffin Jan 06 '21 at 16:55
  • @JerryCoffin Please review my edit. Am I doing fine ? It works well. Afterwards I will mark your answer tick. – Gray Programmerz Jan 06 '21 at 17:37
  • Yeah, what you've added in the edit looks reasonable to me. – Jerry Coffin Jan 06 '21 at 19:06
1

You could process messages dynamically like this:

typedef void (*FHANDLE)();
std::vector<FHANDLE> handles;

void AddHandler(FHANDLE handle)
{
    handles.push_back(handle);
}
void DelHandler(FHANDLE handle)
{
    auto it = std::find(handles.begin(), handles.end(), handle);
    handles.erase(it);
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
    case WM_CREATE:
        for (int i = 0; i < handles.size(); i++)
        {
            handles[i]();
        }
        break;
    ...
    }
}

And add/del the handle:

void myclick1()
{
    MessageBox(0, L"test1", L"message", 0);
}
void myclick2()
{
    MessageBox(0, L"test2", L"message", 0);
}

BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
   hInst = hInstance; // Store instance handle in our global variable
   AddHandler(myclick1);
   AddHandler(myclick2);
   //DelHandler(myclick2);
   HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
       CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);
   ...
}
Drake Wu
  • 6,927
  • 1
  • 7
  • 30